diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8ef28a3..4a7c1e9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,61 +1,136 @@ -# main.yml +name: Build & Release -# Workflow's name -name: Build - -# Workflow's trigger on: push: - branches: - - master + tags: + - v* + workflow_dispatch: + inputs: + test_upload_dist: + description: "Test upload-dist.js script" + required: true + default: false + type: boolean +env: + NODE_VERSION: 22.x -# Workflow's jobs jobs: - # job's id - release: - # job's name - name: build and release electron app - - # the type of machine to run the job on + # parallel build jobs + build: + name: Build on ${{ matrix.os }} runs-on: ${{ matrix.os }} - - # create a build matrix for jobs strategy: + # if one job fails, do not stop other jobs fail-fast: false matrix: - os: [ubuntu-latest, macos-14] - - # create steps + os: [macos-latest, windows-latest, ubuntu-latest] steps: - # step1: check out repository - name: Check out git repository - uses: actions/checkout@v2 - - # step2: install node env - - name: Install Node.js - uses: actions/setup-node@v2 + uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v6 with: - node-version: '16.x' - - - name: Install system deps - if: matrix.os == 'ubuntu-latest' + node-version: ${{ env.NODE_VERSION }} + - name: Clean workspace on Windows + if: runner.os == 'Windows' run: | - sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils + if (Test-Path dist) { Remove-Item -Recurse -Force dist } + if (Test-Path dist_electron) { Remove-Item -Recurse -Force dist_electron } + if (Test-Path node_modules) { Remove-Item -Recurse -Force node_modules } - # step3: yarn - - name: Yarn install + if (Test-Path "$env:LOCALAPPDATA\electron-builder") { + Remove-Item "$env:LOCALAPPDATA\electron-builder" -Recurse -Force -ErrorAction SilentlyContinue + } + if (Test-Path "$env:LOCALAPPDATA\electron") { + Remove-Item "$env:LOCALAPPDATA\electron" -Recurse -Force -ErrorAction SilentlyContinue + } + - name: Clean workspace on macOS & Linux + if: runner.os == 'macOS' || runner.os == 'Linux' run: | - yarn - yarn global add xvfb-maybe - - - name: Build & release app - run: | - yarn release - yarn upload-dist + rm -rf dist dist_electron node_modules ~/.cache/electron-builder ~/.cache/electron + - name: Install dependencies + run: yarn install + - name: Ubuntu Update with sudo + if: runner.os == 'Linux' + run: sudo apt-get update + - name: Build Windows x64 & ARM64 App + if: runner.os == 'Windows' + run: yarn build:win || true 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 }} + - name: Build macOS Universal App + if: runner.os == 'macOS' + run: yarn build:mac || true + shell: bash + env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} + - name: Build Linux x64 & ARM64 App + if: runner.os == 'Linux' + run: yarn build:linux || true + shell: bash + env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: PicGo-${{ runner.os }} + path: dist/*.* + - name: Upload to release.picgo.app + if: startsWith(github.ref, 'refs/tags/v') || github.event.inputs.test_upload_dist + run: | + yarn upload-dist + env: PICGO_ENV_S3_SECRET_ID: ${{ secrets.PICGO_ENV_S3_SECRET_ID }} PICGO_ENV_S3_SECRET_KEY: ${{ secrets.PICGO_ENV_S3_SECRET_KEY }} PICGO_ENV_S3_ACCOUNT_ID: ${{ secrets.PICGO_ENV_S3_ACCOUNT_ID }} + release: + name: Publish GitHub Release + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + - name: Publish GitHub Dev Release + if: github.event_name == 'workflow_dispatch' + uses: softprops/action-gh-release@v2 + continue-on-error: true + with: + token: ${{ secrets.GH_TOKEN }} + tag_name: dev + draft: true + prerelease: false + files: | + !artifacts/**/*-unpacked/** + artifacts/**/*.exe + artifacts/**/*.dmg + artifacts/**/*.zip + artifacts/**/*.AppImage + artifacts/**/*.deb + artifacts/**/*.snap + artifacts/**/*.tar.gz + artifacts/**/*.yml + artifacts/**/*.blockmap + - name: Publish GitHub Release + if: startsWith(github.ref, 'refs/tags/v') + uses: softprops/action-gh-release@v2 + continue-on-error: true + with: + token: ${{ secrets.GH_TOKEN }} + generate_release_notes: true + draft: true + prerelease: false + files: | + !artifacts/**/*-unpacked/** + artifacts/**/*.exe + artifacts/**/*.dmg + artifacts/**/*.zip + artifacts/**/*.AppImage + artifacts/**/*.deb + artifacts/**/*.snap + artifacts/**/*.tar.gz + artifacts/**/*.yml + artifacts/**/*.blockmap diff --git a/.github/workflows/manually.yml b/.github/workflows/manually.yml deleted file mode 100644 index 20d7172..0000000 --- a/.github/workflows/manually.yml +++ /dev/null @@ -1,58 +0,0 @@ -# main.yml - -# Workflow's name -name: Build - -# Workflow's trigger -on: workflow_dispatch - -# Workflow's jobs -jobs: - # job's id - release: - # job's name - name: build and release electron app - - # the type of machine to run the job on - runs-on: ${{ matrix.os }} - - # create a build matrix for jobs - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-14] - - # create steps - steps: - # step1: check out repository - - name: Check out git repository - uses: actions/checkout@v2 - - # step2: install node env - - name: Install Node.js - uses: actions/setup-node@v2 - with: - node-version: '16.x' - - - name: Install system deps - if: matrix.os == 'ubuntu-latest' - run: | - sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils - - # step3: yarn - - name: Yarn install - run: | - yarn - yarn global add xvfb-maybe - - - 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 }} - PICGO_ENV_S3_SECRET_ID: ${{ secrets.PICGO_ENV_S3_SECRET_ID }} - PICGO_ENV_S3_SECRET_KEY: ${{ secrets.PICGO_ENV_S3_SECRET_KEY }} - PICGO_ENV_S3_ACCOUNT_ID: ${{ secrets.PICGO_ENV_S3_ACCOUNT_ID }} diff --git a/.gitignore b/.gitignore index 94a9fc0..f2a1278 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,6 @@ test.js scripts/*.yml #Electron-builder output -/dist_electron \ No newline at end of file +/dist_electron +.serena/ +dist/* \ No newline at end of file diff --git a/.node-version b/.node-version index 3c03207..2bd5a0a 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -18 +22 diff --git a/.vscode/launch.json b/.vscode/launch.json index de810e0..f0be03a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,8 +11,13 @@ "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" }, "preLaunchTask": "electron-debug", - "args": ["--remote-debugging-port=9223", "./dist_electron"], - "outFiles": ["${workspaceFolder}/dist_electron/**/*.js"] + "args": [ + "--remote-debugging-port=9223", + "./dist" + ], + "outFiles": [ + "${workspaceFolder}/dist/**/*.js" + ] }, { "name": "Electron: Renderer", @@ -30,7 +35,10 @@ "compounds": [ { "name": "Electron: All", - "configurations": ["Electron: Main", "Electron: Renderer"] + "configurations": [ + "Electron: Main", + "Electron: Renderer" + ] } ] } \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index ebb4532..357b529 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,14 +5,18 @@ PicGo is an Electron + Vue 3 desktop client. Source lives in `src/`: `src/main` ## Build, Test, and Development Commands - `yarn install` — install dependencies; `npm install` is unsupported because native modules are patched for Yarn. -- `yarn dev` / `yarn electron:serve` — start the hot-reload dev environment for both processes. -- `yarn build` / `yarn electron:build` — package the app into `dist_electron/`; set `ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/` when downloads are slow. +- Always add/remove dependencies with `yarn` (never edit package.json versions by hand then install). +- `yarn dev` — electron-vite dev server for main/preload/renderer. +- `yarn build` — electron-vite build outputs to `dist/main`, `dist/preload`, `dist/renderer`; `yarn preview` for preview mode. +- Packaging config lives in `electron-builder.yml` (read by electron-builder via package.json `build` field/extraResources); set `ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/` if downloads are slow. - `yarn lint` / `yarn lint:fix` — run or auto-fix ESLint (Standard, TypeScript, Vue rules). - `yarn lint:dpdm` — fail fast on circular dependencies in `src/`. - `yarn gen-i18n` — regenerate typed locales after touching `public/i18n/*.yml`. ## Coding Style & Naming Conventions -Follow ESLint Standard defaults: two-space indentation, single quotes, trailing commas where allowed, and no stray semicolons. Author new modules in TypeScript. Keep renderer files browser-safe; route Node APIs through IPC helpers such as `src/main/events/picgoCoreIPC.ts`. Name Vue components in PascalCase (`UploadPanel.vue`) and use camelCase for utilities. Centralize IPC event names inside `src/universal/events/constants.ts`, and store enums/types under `src/universal/types/` so they stay reusable. +Follow ESLint Standard defaults: two-space indentation, single quotes, trailing commas where allowed, and no stray semicolons. Author new modules in TypeScript. Keep renderer files browser-safe; route Node APIs through IPC helpers such as `src/main/events/picgoCoreIPC.ts`. Name Vue components in PascalCase (`UploadPanel.vue`) and use camelCase for utilities. Centralize IPC event names inside `src/universal/events/constants.ts`, and store enums/types under `src/universal/types/` so they stay reusable. Static assets are served from `public/` and resolved via `getStaticPath`/`getStaticFileUrl` (`src/universal/utils/staticPath.ts`); avoid using `__static` directly. +Static assets are served from `public/`. In the main process use `getStaticPath`/`getStaticFileUrl` (`src/universal/utils/staticPath.ts`). In the renderer, place assets under `public/` and resolve them via `import.meta.env.BASE_URL + filename` (helper: `src/renderer/utils/static.ts`); do not rely on `__static` in renderer code. +- Do not use `as any` under any circumstances; keep typings explicit and safe. ## Testing Guidelines Place renderer unit specs in `test/unit/specs` with the `.spec.js` suffix; Karma picks them up via `require.context`. Run them with `npx karma start test/unit/karma.conf.js --single-run` and ensure new renderer folders are covered. Spectron e2e cases live in `test/e2e/specs`; build first (`yarn build`), then run `npx mocha test/e2e/index.js` so Spectron can launch `dist/electron/main.js`. Document any test data, IPC stubs, or fixtures you add to keep suites reproducible. diff --git a/README.md b/README.md index ec237b7..2469875 100644 --- a/README.md +++ b/README.md @@ -103,21 +103,21 @@ PicGo 本体支持如下图床: ```bash ctrl+c # 退出开发模式 -npm run electron:serve # 重新进入开发模式 +npm run dev # 重新进入开发模式 ``` **注:Windows 开发模式运行之后会在底部任务栏的右下角应用区出现 PicGo 的应用图标。** ### 生产模式 -如果你需要自行构建,可以 `npm run electron:build` 开始进行构建。构建成功后,会在 `dist_electron` 目录里出现构建成功的相应安装文件。 +如果你需要自行构建,可以 `npm run build` 开始进行构建。构建成功后,会在 `dist` 目录里出现构建成功的相应安装文件。 -**注意**:如果你的网络环境不太好,可能会出现 `electron-builder` 下载 `electron` 二进制文件失败的情况。这个时候需要在 `npm run electron:build` 之前指定一下 `electron` 的源为国内源: +**注意**:如果你的网络环境不太好,可能会出现 `electron-builder` 下载 `electron` 二进制文件失败的情况。这个时候需要在 build 之前指定一下 `electron` 的源为国内源: ```bash export ELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/" # 在 Windows 上,则可以使用 set ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ (无需引号) -npm run electron:build +npm run build ``` 只需第一次构建的时候指定一下国内源即可。后续构建不需要特地指定。二进制文件下载在 `~/.electron/` 目录下。如果想要更新 `electron` 构建版本,可以删除 `~/.electron/` 目录,然后重新运行上一步,让 `electron-builder `去下载最新的 `electron` 二进制文件。 diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 3bbc79a..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,32 +0,0 @@ -# Commented sections below can be used to run tests on the CI server -# https://simulatedgreg.gitbooks.io/electron-vue/content/en/testing.html#on-the-subject-of-ci-testing -version: 0.1.{build} - -branches: - only: - - master - -image: Visual Studio 2017 -platform: - - x64 - -cache: - - '%APPDATA%\npm-cache' - - '%USERPROFILE%\.electron' - - '%USERPROFILE%\AppData\Local\Yarn\cache' - -init: - - git config --global core.autocrlf input - -install: - - ps: Install-Product node 16 x64 - - git reset --hard HEAD - - yarn - - node --version - -build_script: - #- yarn test - - yarn release - - yarn upload-dist - -test: false diff --git a/build/icons/512x512.png b/build/icons/512x512.png new file mode 100644 index 0000000..4a6e319 Binary files /dev/null and b/build/icons/512x512.png differ diff --git a/electron-builder.config.ts b/electron-builder.config.ts new file mode 100644 index 0000000..d9879f7 --- /dev/null +++ b/electron-builder.config.ts @@ -0,0 +1,96 @@ +/* eslint-disable no-template-curly-in-string */ +import type { Configuration } from 'electron-builder' + +const config: Configuration = { + appId: 'com.molunerfinn.picgo', + productName: 'PicGo', + publish: [ + { + provider: 'github', + owner: 'Molunerfinn', + repo: 'PicGo', + releaseType: 'draft' + } + ], + files: [ + 'dist_electron/**/*', + 'node_modules/**/*', + 'public/**/*', + 'package.json', + 'LICENSE', + 'README.md' + ], + extraResources: [ + { + from: 'public', + to: 'public' + } + ], + dmg: { + contents: [ + { + x: 410, + y: 150, + type: 'link', + path: '/Applications' + }, + { + x: 130, + y: 150, + type: 'file' + } + ] + }, + mac: { + icon: 'build/icons/512x512.png', + extendInfo: { + LSUIElement: 0 + }, + target: [ + { + target: 'dmg', + arch: ['x64', 'arm64'] + } + ], + artifactName: 'PicGo-${version}-${arch}.${ext}' + }, + win: { + icon: 'build/icons/icon.ico', + artifactName: 'PicGo-${version}-${arch}.exe', + target: [ + { + target: 'nsis', + arch: ['x64', 'ia32', 'arm64'] + } + ] + }, + nsis: { + shortcutName: 'PicGo', + oneClick: false, + allowToChangeInstallationDirectory: true, + include: 'build/installer.nsh' + }, + linux: { + executableName: 'PicGo', + icon: 'build/icons/512x512.png', + artifactName: 'PicGo-${version}-${arch}.${ext}', + target: [ + { + target: 'AppImage', + arch: ['x64', 'arm64'] + }, + { + target: 'deb', + arch: ['x64', 'arm64'] + }, + { + target: 'snap', + arch: ['x64'] + } + ], + maintainer: 'Molunerfinn', + category: 'Utility' + } +} + +export default config diff --git a/electron.vite.config.ts b/electron.vite.config.ts new file mode 100644 index 0000000..d1efc57 --- /dev/null +++ b/electron.vite.config.ts @@ -0,0 +1,58 @@ +import { defineConfig, externalizeDepsPlugin } from 'electron-vite' +import vue from '@vitejs/plugin-vue' +// temp for webUtils +import electronRenderer from '@molunerfinn/vite-plugin-electron-renderer' +import { resolve } from 'path' + +const alias = { + '@': resolve(__dirname, 'src/renderer'), + '~': resolve(__dirname, 'src'), + '#': resolve(__dirname, 'src/universal'), + root: resolve(__dirname, '.'), + apis: resolve(__dirname, 'src/main/apis'), + '@core': resolve(__dirname, 'src/main/apis/core') +} + +export default defineConfig({ + main: { + plugins: [externalizeDepsPlugin()], + resolve: { alias }, + build: { + outDir: 'dist_electron/main', + rollupOptions: { + input: { + index: resolve(__dirname, 'src/background.ts') + } + } + } + }, + preload: { + plugins: [externalizeDepsPlugin()], + resolve: { alias }, + build: { + outDir: 'dist_electron/preload', + rollupOptions: { + input: { + index: resolve(__dirname, 'src/preload/index.ts') + } + } + } + }, + renderer: { + root: 'src/renderer', + publicDir: resolve(__dirname, 'src/renderer/public'), + resolve: { alias }, + plugins: [vue(), electronRenderer()], + build: { + outDir: 'dist_electron/renderer', + rollupOptions: { + input: { + index: resolve(__dirname, 'src/renderer/index.html') + } + } + }, + server: { + port: 5173 + } + } +}) diff --git a/package.json b/package.json index 3644bf2..45a1f38 100644 --- a/package.json +++ b/package.json @@ -2,24 +2,31 @@ "name": "picgo", "version": "2.4.0", "private": true, + "main": "dist_electron/main/index.js", + "description": "A powerful & simple image uploader for creators.", + "author": { + "name": "Molunerfinn", + "email": "marksz@teamsz.xyz" + }, "scripts": { - "build": "vue-cli-service electron:build", - "lint": "vue-cli-service lint", + "build": "electron-vite build && electron-builder --config electron-builder.config.ts", + "build:win": "npm run build && electron-builder --config electron-builder.config.ts --win", + "build:mac": "npm run build && electron-builder --config electron-builder.config.ts --mac", + "build:linux": "npm run build && electron-builder --config electron-builder.config.ts --linux", + "lint": "eslint --ext .js,.jsx,.ts,.tsx,.vue src/", "bump": "bump-version", "cz": "git-cz", - "dev": "vue-cli-service electron:serve", - "electron:build": "vue-cli-service electron:build", - "electron:serve": "vue-cli-service electron:serve", + "dev": "electron-vite dev", + "preview": "electron-vite preview", "gen-i18n": "node ./scripts/gen-i18n-types.js", "lint:fix": "eslint --fix --ext .js,.jsx,.ts,.tsx,.vue src/", "postinstall": "electron-builder install-app-deps", "postuninstall": "electron-builder install-app-deps", - "release": "vue-cli-service electron:build --publish always", "upload-dist": "node ./scripts/upload-dist.js", "lint:dpdm": "dpdm -T --tsconfig ./tsconfig.json --no-tree --no-warning --exit-code circular:1 src/background.ts" }, "dependencies": { - "@element-plus/icons-vue": "^2.0.10", + "@element-plus/icons-vue": "^2.3.2", "@picgo/i18n": "^1.0.0", "@picgo/store": "^2.1.0", "axios": "^0.19.0", @@ -48,7 +55,7 @@ "devDependencies": { "@aws-sdk/client-s3": "^3.276.0", "@aws-sdk/lib-storage": "^3.276.0", - "@babel/plugin-proposal-optional-chaining": "^7.16.7", + "@molunerfinn/vite-plugin-electron-renderer": "^0.14.7", "@picgo/bump-version": "^1.1.2", "@types/electron-devtools-installer": "^2.2.0", "@types/fs-extra": "^9.0.13", @@ -56,7 +63,7 @@ "@types/js-yaml": "^4.0.5", "@types/lowdb": "^1.0.9", "@types/multer": "^1.4.12", - "@types/node": "^16.10.2", + "@types/node": "^20", "@types/request-promise-native": "^1.0.17", "@types/semver": "^7.3.8", "@types/tunnel": "^0.0.3", @@ -64,21 +71,18 @@ "@types/write-file-atomic": "^4.0.0", "@typescript-eslint/eslint-plugin": "^5.48.0", "@typescript-eslint/parser": "^5.48.0", - "@vue/cli-plugin-babel": "^5.0.8", - "@vue/cli-plugin-eslint": "^5.0.8", - "@vue/cli-plugin-router": "^5.0.8", - "@vue/cli-plugin-typescript": "^5.0.8", - "@vue/cli-service": "^5.0.8", - "@vue/eslint-config-standard": "^8.0.1", - "@vue/eslint-config-typescript": "^11.0.2", - "@vue/runtime-dom": "^3.2.45", + "@vitejs/plugin-vue": "^6.0.2", + "@vue/eslint-config-standard": "8.0.1", + "@vue/eslint-config-typescript": "11.0.2", "autoprefixer": "^10.4.14", "conventional-changelog": "^3.1.18", "cz-customizable": "^6.2.0", "dotenv": "^16.0.1", "dpdm": "^3.13.1", - "electron": "^16.0.6", + "electron": "^38", + "electron-builder": "26.0.12", "electron-devtools-installer": "^3.2.0", + "electron-vite": "^4.0.1", "eslint": "^8.31.0", "eslint-config-standard": ">=16.0.0", "eslint-plugin-import": "^2.24.2", @@ -91,7 +95,7 @@ "stylus-loader": "^3.0.2", "tailwindcss": "^3.3.2", "typescript": "^4.4.3", - "vue-cli-plugin-electron-builder": "^3.0.0-alpha.4" + "vite": "^7.2.6" }, "commitlint": { "extends": [ @@ -110,9 +114,5 @@ "hooks": { "commit-msg": "npm run lint:dpdm && commitlint -E HUSKY_GIT_PARAMS" } - }, - "resolutions": { - "@types/node": "^16.10.2", - "vue-cli-plugin-electron-builder/**/electron-builder": "23.3.3" } } diff --git a/scripts/config.js b/scripts/config.js index 43e6a46..bb02929 100644 --- a/scripts/config.js +++ b/scripts/config.js @@ -1,6 +1,6 @@ // different platform has different format -// macos +// macos (dmg, x64 + arm64) const darwin = [{ appNameWithPrefix: 'PicGo-', ext: '.dmg', @@ -13,33 +13,49 @@ const darwin = [{ 'version-file': 'latest-mac.yml' }] +// linux (AppImage, deb, snap) const linux = [{ appNameWithPrefix: 'PicGo-', ext: '.AppImage', - arch: '', + arch: '-arm64', + 'version-file': 'latest-linux-arm64.yml' +}, { + appNameWithPrefix: 'PicGo-', + ext: '.AppImage', + arch: '-x64', 'version-file': 'latest-linux.yml' }, { - appNameWithPrefix: 'picgo_', + appNameWithPrefix: 'PicGo-', + ext: '.deb', + arch: '-arm64', + 'version-file': 'latest-linux-arm64.yml' +}, { + appNameWithPrefix: 'PicGo-', + ext: '.deb', + arch: '-x64', + 'version-file': 'latest-linux.yml' +}, { + appNameWithPrefix: 'PicGo-', ext: '.snap', - arch: '_amd64', + arch: '-x64', 'version-file': 'latest-linux.yml' }] -// windows +// windows (nsis, x64 + ia32 + arm64) const win32 = [{ - appNameWithPrefix: 'PicGo-Setup-', + appNameWithPrefix: 'PicGo-', ext: '.exe', arch: '-ia32', 'version-file': 'latest.yml' }, { - appNameWithPrefix: 'PicGo-Setup-', + appNameWithPrefix: 'PicGo-', ext: '.exe', arch: '-x64', 'version-file': 'latest.yml' }, { - appNameWithPrefix: 'PicGo-Setup-', + appNameWithPrefix: 'PicGo-', ext: '.exe', - arch: '', // 32 & 64 + arch: '-arm64', 'version-file': 'latest.yml' }] diff --git a/scripts/cos-link.js b/scripts/cos-link.js index 0f9c133..f65c825 100644 --- a/scripts/cos-link.js +++ b/scripts/cos-link.js @@ -1,23 +1,39 @@ const pkg = require('../package.json') const version = pkg.version -// TODO: use the same name format -const generateURL = (platform, ext, prefix = 'PicGo-') => { - return `https://picgo-release.molunerfinn.com/${version}/${prefix}${version}${platform}${ext}` -} -const platformExtList = [ - ['-arm64', '.dmg', 'PicGo-'], - ['-x64', '.dmg', 'PicGo-'], - ['', '.AppImage', 'PicGo-'], - ['-ia32', '.exe', 'PicGo-Setup-'], - ['-x64', '.exe', 'PicGo-Setup-'], - ['', '.exe', 'PicGo-Setup-'], - ['_amd64', '.snap', 'picgo_'] +const generateURL = (arch, ext, prefix = 'PicGo-') => `https://release.picgo.app/${version}/${prefix}${version}${arch}${ext}` + +const windows = [ + { label: '32 bit', arch: '-ia32', ext: '.exe' }, + { label: '64 bit', arch: '-x64', ext: '.exe' }, + { label: 'ARM64', arch: '-arm64', ext: '.exe' } ] -const links = platformExtList.map(([arch, ext, prefix]) => { - const markdownLink = `[${prefix}${version}${arch}${ext}](${generateURL(arch, ext, prefix)})` - return markdownLink -}) +const macos = [ + { label: 'Intel', arch: '-x64', ext: '.dmg' }, + { label: 'Apple Silicon', arch: '-arm64', ext: '.dmg' } +] -console.log(links.join('\n')) +const linux = { + AppImage: [ + { label: '64 bit', arch: '-x64', ext: '.AppImage' }, + { label: 'ARM64', arch: '-arm64', ext: '.AppImage' } + ], + Deb: [ + { label: '64 bit', arch: '-x64', ext: '.deb' }, + { label: 'ARM64', arch: '-arm64', ext: '.deb' } + ], + Snap: [ + { label: '64 bit', arch: '-x64', ext: '.snap' } + ] +} + +const renderLine = (items) => items.map(({ label, arch, ext }) => `[${label}](${generateURL(arch, ext)})`).join(' | ') + +const sections = [ + `### Windows\n- ${renderLine(windows)}`, + `### macOS\n- ${renderLine(macos)}`, + `### Linux\n- AppImage: ${renderLine(linux.AppImage)}\n- Deb: ${renderLine(linux.Deb)}\n- Snap: ${renderLine(linux.Snap)}` +] + +console.log(sections.join('\n\n')) diff --git a/scripts/upload-dist.js b/scripts/upload-dist.js index d33eba0..5c0f55a 100644 --- a/scripts/upload-dist.js +++ b/scripts/upload-dist.js @@ -9,14 +9,14 @@ const pkg = require('../package.json') const configList = require('./config') const mime = require('mime-types') const path = require('path') -const distPath = path.join(__dirname, '../dist_electron') +const distPath = path.join(__dirname, '../dist') const S3Client = require('@aws-sdk/client-s3').S3Client const Upload = require('@aws-sdk/lib-storage').Upload // const BUCKET = 'picgo-1251750343' // const COS_SECRET_ID = process.env.PICGO_ENV_COS_SECRET_ID // const COS_SECRET_KEY = process.env.PICGO_ENV_COS_SECRET_KEY -const S3_BUCKET = 'picgo' +const S3_BUCKET = 'release' // const AREA = 'ap-chengdu' const VERSION = pkg.version const FILE_PATH = `${VERSION}/` @@ -126,23 +126,24 @@ const uploadDist = async () => { if (VERSION.toLocaleLowerCase().includes('beta')) { versionFileName = versionFileName.replace('.yml', '.beta.yml') } - const client = new S3Client(S3Options) - const uploadDistToS3 = new Upload({ - client, - params: { - Bucket: S3_BUCKET, - Key: `${FILE_PATH}${fileName}`, - Body: fs.createReadStream(filePath), - ContentType: 'application/octet-stream' - } - }) - // upload dist file - console.log('[PicGo Dist] Uploading...', fileName, `${index + 1}/${configList[platform].length}`) - uploadDistToS3.on('httpUploadProgress', progress => { - console.log(`[PicGo Dist] Uploading... ${progress.loaded}/${progress.total}`) - }) - await uploadDistToS3.done() + if (fs.existsSync(filePath)) { + const uploadDistToS3 = new Upload({ + client, + params: { + Bucket: S3_BUCKET, + Key: `${FILE_PATH}${fileName}`, + Body: fs.createReadStream(filePath), + ContentType: 'application/octet-stream' + } + }) + // upload dist file + console.log('[PicGo Dist] Uploading...', fileName, `${index + 1}/${configList[platform].length}`) + uploadDistToS3.on('httpUploadProgress', progress => { + console.log(`[PicGo Dist] Uploading... ${progress.loaded}/${progress.total}`) + }) + await uploadDistToS3.done() + } // upload version file if (!versionFileHasUploaded) { @@ -158,6 +159,7 @@ const uploadDist = async () => { console.log('[PicGo Version File] Uploading...', versionFileName) await uploadVersionFileToS3.done() versionFileHasUploaded = true + console.log('[PicGo Version File] Upload successfully') } } } else { diff --git a/src/background.ts b/src/background.ts index bec85b2..1123a58 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,5 +1,7 @@ +import { initStaticPath } from '~/main/utils/env' import { bootstrap } from '~/main/lifeCycle' +initStaticPath() bootstrap.launchApp() /** diff --git a/src/main/apis/app/system/index.ts b/src/main/apis/app/system/index.ts index 5172823..61852f4 100644 --- a/src/main/apis/app/system/index.ts +++ b/src/main/apis/app/system/index.ts @@ -20,6 +20,7 @@ import { T } from '~/main/i18n' import { isMacOSVersionGreaterThanOrEqualTo } from '~/main/utils/getMacOSVersion' import { buildPicBedListMenu } from '~/main/events/remotes/menu' import { isLinux, isMacOS } from '~/universal/utils/common' +import { getStaticPath } from '#/utils/staticPath' let contextMenu: Menu | null let menu: Menu | null let tray: Tray | null @@ -139,10 +140,10 @@ const getTrayIcon = () => { if (process.platform === 'darwin') { const isMacOSGreaterThan11 = isMacOSVersionGreaterThanOrEqualTo('11') return isMacOSGreaterThan11 - ? `${__static}/menubar-newdarwinTemplate.png` - : `${__static}/menubar.png` + ? getStaticPath('menubar-newdarwinTemplate.png') + : getStaticPath('menubar.png') } else { - return `${__static}/menubar-nodarwin.png` + return getStaticPath('menubar-nodarwin.png') } } @@ -217,9 +218,9 @@ export function createTray () { tray.on('drag-enter', () => { if (nativeTheme.shouldUseDarkColors) { - tray!.setImage(`${__static}/upload-dark.png`) + tray!.setImage(getStaticPath('upload-dark.png')) } else { - tray!.setImage(`${__static}/upload.png`) + tray!.setImage(getStaticPath('upload.png')) } }) diff --git a/src/main/apis/app/uploader/index.ts b/src/main/apis/app/uploader/index.ts index ef410f7..fa11e73 100644 --- a/src/main/apis/app/uploader/index.ts +++ b/src/main/apis/app/uploader/index.ts @@ -22,11 +22,12 @@ import { privacyManager } from '~/main/utils/privacyManager' import writeFile from 'write-file-atomic' import { CLIPBOARD_IMAGE_FOLDER } from '~/universal/utils/static' import { cleanupFormUploaderFiles } from '~/main/utils/cleanupFormUploaderFiles' +import { IpcMainEvent } from 'electron/main' const waitForRename = (window: BrowserWindow, id: number): Promise => { return new Promise((resolve) => { const windowId = window.id - ipcMain.once(`${RENAME_FILE_NAME}${id}`, (evt: Event, newName: string) => { + ipcMain.once(`${RENAME_FILE_NAME}${id}`, (evt: IpcMainEvent, newName: string) => { resolve(newName) window.close() }) diff --git a/src/main/apis/app/window/constants.ts b/src/main/apis/app/window/constants.ts index f0ca4c7..ba4cbbe 100644 --- a/src/main/apis/app/window/constants.ts +++ b/src/main/apis/app/window/constants.ts @@ -1,21 +1,12 @@ const isDevelopment = process.env.NODE_ENV !== 'production' +import { buildRendererUrl } from '~/main/utils/env' -export const TRAY_WINDOW_URL = isDevelopment - ? (process.env.WEBPACK_DEV_SERVER_URL as string) - : 'picgo://./index.html' +export const TRAY_WINDOW_URL = buildRendererUrl() -export const SETTING_WINDOW_URL = isDevelopment - ? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#main-page/upload` - : 'picgo://./index.html#main-page/upload' +export const SETTING_WINDOW_URL = buildRendererUrl('main-page/upload') -export const MINI_WINDOW_URL = isDevelopment - ? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#mini-page` - : 'picgo://./index.html#mini-page' +export const MINI_WINDOW_URL = buildRendererUrl('mini-page') -export const RENAME_WINDOW_URL = process.env.NODE_ENV === 'development' - ? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#rename-page` - : 'picgo://./index.html#rename-page' +export const RENAME_WINDOW_URL = buildRendererUrl('rename-page') -export const TOOLBOX_WINDOW_URL = process.env.NODE_ENV === 'development' - ? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#toolbox-page` - : 'picgo://./index.html#toolbox-page' +export const TOOLBOX_WINDOW_URL = buildRendererUrl('toolbox-page') diff --git a/src/main/apis/app/window/windowList.ts b/src/main/apis/app/window/windowList.ts index 4cf00c9..be1119e 100644 --- a/src/main/apis/app/window/windowList.ts +++ b/src/main/apis/app/window/windowList.ts @@ -1,3 +1,4 @@ +// import path from 'path' import { SETTING_WINDOW_URL, TRAY_WINDOW_URL, @@ -13,10 +14,19 @@ import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants' import { app } from 'electron' import { T } from '~/main/i18n' import { isLinux } from '~/universal/utils/common' +import { getStaticPath } from '#/utils/staticPath' // import { URLSearchParams } from 'url' const windowList = new Map() +const defaultWebPreferences = { + // preload: path.join(__dirname, '../preload/index.js'), + nodeIntegration: true, + contextIsolation: false, + nodeIntegrationInWorker: true, + backgroundThrottling: false +} + const handleWindowParams = (windowURL: string) => { // const [baseURL, hash = ''] = windowURL.split('#') // const search = new URLSearchParams() @@ -55,10 +65,7 @@ windowList.set(IWindowList.TRAY_WINDOW, { transparent: true, vibrancy: 'ultra-dark', webPreferences: { - nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION, - contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION, - nodeIntegrationInWorker: true, - backgroundThrottling: false, + ...defaultWebPreferences, webSecurity: false } } @@ -85,24 +92,18 @@ windowList.set(IWindowList.SETTING_WINDOW, { fullscreenable: false, resizable: false, title: 'PicGo', - vibrancy: 'ultra-dark', transparent: true, skipTaskbar: !showDockIcon, titleBarStyle: 'hidden', webPreferences: { - backgroundThrottling: false, - nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION, - contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION, - nodeIntegrationInWorker: true, + ...defaultWebPreferences, webSecurity: false } } if (process.platform !== 'darwin') { options.show = false options.frame = false - options.backgroundColor = '#3f3c37' - options.transparent = false - options.icon = `${__static}/logo.png` + options.icon = getStaticPath('logo.png') options.skipTaskbar = false } return options @@ -135,12 +136,9 @@ windowList.set(IWindowList.MINI_WINDOW, { skipTaskbar: true, resizable: false, transparent: process.platform !== 'linux', - icon: `${__static}/logo.png`, + icon: getStaticPath('logo.png'), webPreferences: { - backgroundThrottling: false, - nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION, - contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION, - nodeIntegrationInWorker: true + ...defaultWebPreferences } } @@ -164,19 +162,14 @@ windowList.set(IWindowList.RENAME_WINDOW, { show: true, fullscreenable: false, resizable: false, - vibrancy: 'ultra-dark', + backgroundColor: 'rgba(26,40,42,0.9)', webPreferences: { - nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION, - contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION, - nodeIntegrationInWorker: true, - backgroundThrottling: false + ...defaultWebPreferences } } if (process.platform !== 'darwin') { options.show = true - options.backgroundColor = '#3f3c37' options.autoHideMenuBar = true - options.transparent = false } return options }, @@ -211,21 +204,16 @@ windowList.set(IWindowList.TOOLBOX_WINDOW, { center: true, fullscreenable: false, resizable: false, - title: `PicGo ${T('TOOLBOX')}`, - vibrancy: 'ultra-dark', - icon: `${__static}/logo.png`, + backgroundColor: 'rgba(26,40,42,0.9)', + title: `PicGo-${T('TOOLBOX')}`, + icon: getStaticPath('logo.png'), webPreferences: { - backgroundThrottling: false, - nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION, - contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION, - nodeIntegrationInWorker: true, + ...defaultWebPreferences, webSecurity: false } } if (process.platform !== 'darwin') { - options.backgroundColor = '#3f3c37' options.autoHideMenuBar = true - options.transparent = false } return options }, diff --git a/src/main/events/ipcList.ts b/src/main/events/ipcList.ts index b444046..75a3063 100644 --- a/src/main/events/ipcList.ts +++ b/src/main/events/ipcList.ts @@ -167,7 +167,9 @@ export default { server.restart() }) ipcMain.on(OPEN_DEVTOOLS, (event: IpcMainEvent) => { - event.sender.openDevTools() + event.sender.openDevTools({ + mode: 'detach' + }) }) // menu & window methods ipcMain.on(SHOW_MINI_PAGE_MENU, () => { diff --git a/src/main/events/picgoCoreIPC.ts b/src/main/events/picgoCoreIPC.ts index c8e6cda..dc4ae83 100644 --- a/src/main/events/picgoCoreIPC.ts +++ b/src/main/events/picgoCoreIPC.ts @@ -7,6 +7,7 @@ import { ipcMain, clipboard } from 'electron' +import fs from 'fs-extra' import { IPasteStyle, IPicGoHelperType, IWindowList } from '#/types/enum' import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler' import picgo from '@core/picgo' @@ -39,11 +40,7 @@ import pasteTemplate from '../utils/pasteTemplate' import { i18nManager, T } from '~/main/i18n' import { rpcServer } from './rpc' -// eslint-disable-next-line -const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require -// const PluginHandler = requireFunc('picgo/lib/PluginHandler').default const STORE_PATH = path.dirname(dbPathChecker()) -// const CONFIG_PATH = path.join(STORE_PATH, '/data.json') interface GuiMenuItem { label: string @@ -84,7 +81,7 @@ const getPluginList = (): IPicGoPlugin[] => { for (const i in pluginList) { const plugin = picgo.pluginLoader.getPlugin(pluginList[i])! const pluginPath = path.join(STORE_PATH, `/node_modules/${pluginList[i]}`) - const pluginPKG = requireFunc(path.join(pluginPath, 'package.json')) + const pluginPKG = fs.readJSONSync(path.join(pluginPath, 'package.json'), 'utf-8') const uploaderName = plugin.uploader || '' const transformerName = plugin.transformer || '' let menu: Omit[] = [] diff --git a/src/main/events/remotes/menu.ts b/src/main/events/remotes/menu.ts index 37ed67f..267f3f3 100644 --- a/src/main/events/remotes/menu.ts +++ b/src/main/events/remotes/menu.ts @@ -105,7 +105,9 @@ const buildMainPageMenu = (win: BrowserWindow) => { { label: T('SHOW_DEVTOOLS'), click () { - win?.webContents?.openDevTools() + win?.webContents?.openDevTools({ + mode: 'detach' + }) } }, { diff --git a/src/main/i18n/index.ts b/src/main/i18n/index.ts index fbb4823..b2b8370 100644 --- a/src/main/i18n/index.ts +++ b/src/main/i18n/index.ts @@ -2,11 +2,12 @@ import yaml from 'js-yaml' import { ObjectAdapter, I18n } from '@picgo/i18n' import path from 'path' import fs from 'fs-extra' +import { getStaticPath } from '#/utils/staticPath' import { builtinI18nList } from '#/i18n' class I18nManager { private i18n: I18n | null = null - private builtinI18nFolder = path.join(__static, 'i18n') + private builtinI18nFolder = getStaticPath('i18n') private outerI18nFolder = '' private localesMap: Map = new Map() private currentLanguage: string = 'zh-CN' diff --git a/src/main/lifeCycle/index.ts b/src/main/lifeCycle/index.ts index d42c245..d2fde82 100644 --- a/src/main/lifeCycle/index.ts +++ b/src/main/lifeCycle/index.ts @@ -5,9 +5,6 @@ import { protocol, Notification } from 'electron' -import { - createProtocol -} from 'vue-cli-plugin-electron-builder/lib' import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer' import beforeOpen from '~/main/utils/beforeOpen' import ipcList from '~/main/events/ipcList' @@ -38,8 +35,9 @@ import { initI18n } from '~/main/utils/handleI18n' import { remoteNoticeHandler } from 'apis/app/remoteNotice' import { isMacOS } from '../utils/getMacOSVersion' import { isWindowShouldShowOnStartup } from '../apis/app/window/windowList' +import { initStaticPath, isDev } from '../utils/env' -const isDevelopment = process.env.NODE_ENV !== 'production' +const isDevelopment = isDev const handleStartUpFiles = (argv: string[], cwd: string) => { const files = getUploadFiles(argv, cwd, logger) @@ -74,7 +72,6 @@ class LifeCycle { private onReady () { const readyFunction = async () => { console.log('on ready') - createProtocol('picgo') if (isDevelopment && !process.env.IS_TEST) { // Install Vue Devtools try { @@ -140,7 +137,6 @@ class LifeCycle { } }) app.on('activate', () => { - createProtocol('picgo') if (!windowManager.has(IWindowList.TRAY_WINDOW)) { windowManager.create(IWindowList.TRAY_WINDOW) } @@ -200,6 +196,7 @@ class LifeCycle { if (!gotTheLock) { app.quit() } else { + initStaticPath() await this.beforeReady() this.onReady() this.onRunning() diff --git a/src/main/utils/beforeOpen.ts b/src/main/utils/beforeOpen.ts index d760f0c..799f2c0 100644 --- a/src/main/utils/beforeOpen.ts +++ b/src/main/utils/beforeOpen.ts @@ -5,6 +5,7 @@ import { dbPathChecker } from 'apis/core/datastore/dbChecker' import yaml from 'js-yaml' import { i18nManager } from '~/main/i18n' // import { ILocales } from '~/universal/types/i18n' +import { getStaticPath } from '#/utils/staticPath' const configPath = dbPathChecker() const CONFIG_DIR = path.dirname(configPath) @@ -49,7 +50,7 @@ function resolveMacWorkFlow () { return true } else { try { - copyFileOutsideOfElectronAsar(path.join(__static, 'Upload pictures with PicGo.workflow'), dest) + copyFileOutsideOfElectronAsar(getStaticPath('Upload pictures with PicGo.workflow'), dest) } catch (e) { console.log(e) } @@ -87,16 +88,16 @@ function resolveClipboardImageGenerator () { function getClipboardFiles () { const files = [ - '/linux.sh', - '/mac.applescript', - '/windows.ps1', - '/windows10.ps1', - '/wsl.sh' + 'linux.sh', + 'mac.applescript', + 'windows.ps1', + 'windows10.ps1', + 'wsl.sh' ] return files.map(item => { return { - origin: path.join(__static, item), + origin: getStaticPath(item), dest: path.join(CONFIG_DIR, item) } }) diff --git a/src/main/utils/env.ts b/src/main/utils/env.ts new file mode 100644 index 0000000..6d3a6d7 --- /dev/null +++ b/src/main/utils/env.ts @@ -0,0 +1,31 @@ +import path from 'path' +import { app } from 'electron' +import { pathToFileURL } from 'url' + +export const isDev = !app.isPackaged + +const defaultStaticPath = process.env.STATIC_PATH || (isDev + ? path.join(process.cwd(), 'public') + : path.join(process.resourcesPath, 'public')) + +process.env.STATIC_PATH = defaultStaticPath + +const rendererHtmlPath = path.join(__dirname, '../renderer/index.html') + +export const initStaticPath = () => defaultStaticPath + +export const getRendererBaseUrl = () => { + if (isDev && process.env.ELECTRON_RENDERER_URL) { + return process.env.ELECTRON_RENDERER_URL + } + return pathToFileURL(rendererHtmlPath).toString() +} + +export const buildRendererUrl = (hash?: string) => { + const base = getRendererBaseUrl() + if (!hash) return base + const cleanedHash = hash.startsWith('#') ? hash : `#${hash}` + return `${base}${cleanedHash}` +} + +export const getStaticPath = () => process.env.STATIC_PATH || defaultStaticPath diff --git a/src/preload/index.ts b/src/preload/index.ts new file mode 100644 index 0000000..f9893b6 --- /dev/null +++ b/src/preload/index.ts @@ -0,0 +1,13 @@ +// temp no used +// will be refactor in future +import { contextBridge, webUtils } from 'electron' + +const getFilePath = (file: File): string => webUtils.getPathForFile(file) + +const electronApi = { + getFilePath +} + +contextBridge.exposeInMainWorld('electronApi', electronApi) + +export type ElectronApi = typeof electronApi diff --git a/src/renderer/hooks/useOS.ts b/src/renderer/hooks/useOS.ts new file mode 100644 index 0000000..936f655 --- /dev/null +++ b/src/renderer/hooks/useOS.ts @@ -0,0 +1,10 @@ +import { onBeforeMount, ref } from 'vue' + +export const useOS = () => { + const os = ref('') + + onBeforeMount(() => { + os.value = process.platform + }) + return os +} diff --git a/src/renderer/index.html b/src/renderer/index.html new file mode 100644 index 0000000..2b595ed --- /dev/null +++ b/src/renderer/index.html @@ -0,0 +1,15 @@ + + + + + + + + + PicGo + + +
+ + + diff --git a/src/renderer/layouts/Main.vue b/src/renderer/layouts/Main.vue index 27f2a3a..b5b6264 100644 --- a/src/renderer/layouts/Main.vue +++ b/src/renderer/layouts/Main.vue @@ -2,7 +2,6 @@
PicGo - {{ version }} @@ -103,7 +102,6 @@ :offset="5" style="height: 428px" class="main-wrapper" - :class="{ 'darwin': os === 'darwin' }" > = ref([]) const qrcodeVisible = ref(false) @@ -250,7 +249,6 @@ const choosedPicBedForQRCode: Ref = ref([]) const keepAlivePages = $router.getRoutes().filter(item => item.meta.keepAlive).map(item => item.name as string) onBeforeMount(() => { - os.value = process.platform sendToMain(GET_PICBEDS) ipcRenderer.on(GET_PICBEDS, getPicBeds) handleGetPicPeds() @@ -350,7 +348,8 @@ export default { }