🐛 Fix(custom): the issue that x64 macOS app can't be opened

ISSUES CLOSED: #1363
This commit is contained in:
PiEgg
2025-12-10 23:17:53 +08:00
parent 2cc29833df
commit 54d15a6749
17 changed files with 12026 additions and 98 deletions
-4
View File
@@ -1,4 +0,0 @@
test/unit/coverage/**
test/unit/*.js
test/e2e/*.js
dist/
-38
View File
@@ -1,38 +0,0 @@
module.exports = {
root: true,
globals: {
__static: 'readonly'
},
env: {
node: true
},
parser: 'vue-eslint-parser',
extends: [
'plugin:vue/vue3-recommended',
'@vue/standard',
'@vue/typescript'
],
plugins: ['@typescript-eslint'],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'off' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
indent: 'off',
'no-async-promise-executor': 'off',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/indent': ['error', 2],
'vue/no-v-html': 'off'
},
parserOptions: {
parser: '@typescript-eslint/parser'
},
overrides: [
{
files: ['*.ts', '*.vue'],
rules: {
'no-undef': 'off' // https://typescript-eslint.io/docs/linting/troubleshooting/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
}
}
],
ignorePatterns: ['src/**/*.d.ts']
}
+1
View File
@@ -0,0 +1 @@
shamefully-hoist=true
+10 -10
View File
@@ -4,14 +4,14 @@
PicGo is an Electron + Vue 3 desktop client. Source lives in `src/`: `src/main` for main-process and IPC logic, `src/renderer` for Vue views, and `src/universal` for shared helpers (`types/`, `events/constants.ts`). `background.ts` wires Electron Builder. Static assets and locale YAML files stay in `public/` (add languages under `public/i18n/`), while `docs/` hosts user-facing guides. Automation scripts live in `scripts/`, and legacy tests sit under `test/unit` (Karma) and `test/e2e` (Spectron).
## Build, Test, and Development Commands
- `yarn install` — install dependencies; `npm install` is unsupported because native modules are patched for Yarn.
- 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.
- `pnpm install` — install dependencies; `npm install` is unsupported.
- Always add/remove dependencies with `pnpm` (never edit package.json versions by hand then install).
- `pnpm dev` — electron-vite dev server for main/preload/renderer.
- `pnpm build` — electron-vite build outputs to `dist/main`, `dist/preload`, `dist/renderer`; `pnpm 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`.
- `pnpm lint` / `pnpm lint:fix` — run or auto-fix ESLint (Standard, TypeScript, Vue rules).
- `pnpm lint:dpdm` — fail fast on circular dependencies in `src/`.
- `pnpm 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. Static assets are served from `public/` and resolved via `getStaticPath`/`getStaticFileUrl` (`src/universal/utils/staticPath.ts`); avoid using `__static` directly.
@@ -19,10 +19,10 @@ Static assets are served from `public/`. In the main process use `getStaticPath`
- 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.
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 (`pnpm 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.
## Commit & Pull Request Guidelines
Commits follow the PicGo conventional preset enforced by Husky (`yarn lint:dpdm` + Commitlint). Stage your changes and run `yarn cz` to craft messages that pass CI. Pull requests should explain the change, link related issues, and attach UI screenshots or recordings. Note how you validated the work (dev server, build, Karma, Spectron) and call out migration or configuration steps reviewers must perform.
Commits follow the PicGo conventional preset enforced by Husky (`pnpm lint:dpdm` + Commitlint). Stage your changes and run `pnpm cz` to craft messages that pass CI. Pull requests should explain the change, link related issues, and attach UI screenshots or recordings. Note how you validated the work (dev server, build, Karma, Spectron) and call out migration or configuration steps reviewers must perform.
## Internationalization Tips
Add locales by creating `public/i18n/<locale>.yml`, exposing its `LANG_DISPLAY_LABEL`, and registering it in `src/universal/i18n/index.ts`. Finish with `yarn gen-i18n` so the generated typings stay in sync.
Add locales by creating `public/i18n/<locale>.yml`, exposing its `LANG_DISPLAY_LABEL`, and registering it in `src/universal/i18n/index.ts`. Finish with `pnpm gen-i18n` so the generated typings stay in sync.
+3 -4
View File
@@ -1,4 +1,4 @@
/* eslint-disable no-template-curly-in-string */
import type { Configuration } from 'electron-builder'
const config: Configuration = {
@@ -16,11 +16,10 @@ const config: Configuration = {
publish: [],
files: [
'dist_electron/**/*',
'node_modules/**/*',
'public/**/*',
'package.json',
'LICENSE',
'README.md'
'!node_modules/@babel/**/*',
"!**/node_modules/typescript{,/**}"
],
extraResources: [
{
+126
View File
@@ -0,0 +1,126 @@
const path = require('node:path')
const eslintJs = require('@eslint/js')
const tsPlugin = require('@typescript-eslint/eslint-plugin')
const tsParser = require('@typescript-eslint/parser')
const importPlugin = require('eslint-plugin-import')
const promisePlugin = require('eslint-plugin-promise')
const vuePlugin = require('eslint-plugin-vue')
const isProduction = process.env.NODE_ENV === 'production'
const vueConfigs = vuePlugin.configs['flat/recommended'].map(config => ({
...config,
languageOptions: {
...(config.languageOptions || {}),
parserOptions: {
...(config.languageOptions?.parserOptions || {}),
parser: tsParser,
ecmaVersion: 'latest',
sourceType: 'module',
extraFileExtensions: ['.vue']
}
}
}))
const tsConfigs = tsPlugin.configs['flat/recommended'].map(config => ({
...config,
files: config.files || ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'],
languageOptions: {
...(config.languageOptions || {}),
parser: tsParser,
parserOptions: {
...(config.languageOptions?.parserOptions || {}),
ecmaVersion: 'latest',
sourceType: 'module',
extraFileExtensions: ['.vue'],
project: path.join(__dirname, 'tsconfig.json'),
tsconfigRootDir: __dirname
}
},
rules: {
...(config.rules || {}),
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrors: 'none'
}],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-require-imports': 'off',
'@typescript-eslint/no-unsafe-function-type': 'off',
'@typescript-eslint/no-empty-object-type': 'off'
}
}))
module.exports = [
{
ignores: [
'dist/**',
'dist_electron/**',
'build/**',
'test/unit/coverage/**',
'test/unit/*.js',
'test/e2e/*.js',
'node_modules/**'
]
},
{
name: 'eslint/base',
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: {
__static: 'readonly'
}
},
plugins: {
import: importPlugin,
promise: promisePlugin
},
settings: {
'import/resolver': {
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.d.ts', '.vue']
}
}
},
rules: {
...eslintJs.configs.recommended.rules,
...importPlugin.configs.recommended.rules,
...promisePlugin.configs.recommended.rules,
'import/named': 'off',
'import/no-named-as-default-member': 'off',
'import/no-unresolved': 'off',
'promise/catch-or-return': 'off',
'promise/always-return': 'off',
'no-console': 'off',
'no-debugger': isProduction ? 'error' : 'off',
'no-async-promise-executor': 'off',
'no-empty': ['error', { allowEmptyCatch: true }],
'no-unused-vars': 'off'
}
},
...vueConfigs,
{
files: ['*.vue', '**/*.vue'],
rules: {
'vue/no-v-html': 'off',
'vue/attribute-hyphenation': 'off'
}
},
...tsConfigs,
{
files: ['**/*.{ts,tsx,vue}'],
rules: {
'no-undef': 'off'
}
},
{
files: ['**/*.d.ts'],
rules: {
'no-var': 'off',
'@typescript-eslint/no-empty-object-type': 'off',
'@typescript-eslint/no-explicit-any': 'off'
}
}
]
+17 -14
View File
@@ -14,6 +14,7 @@
"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/",
"tsc": "tsc --noEmit",
"bump": "bump-version",
"cz": "git-cz",
"dev": "electron-vite dev",
@@ -33,19 +34,23 @@
"clip-filepaths": "^0.3.0",
"compare-versions": "^4.1.3",
"core-js": "^3.27.1",
"dayjs": "^1.11.19",
"element-plus": "^2.3.7",
"epipebomb": "^1.0.0",
"fs-extra": "^10.0.0",
"js-yaml": "^4.1.0",
"keycode": "^2.2.0",
"lodash": "^4.17.21",
"lodash-id": "^0.14.0",
"lowdb": "^1.0.0",
"marked": "^7.0.4",
"mitt": "^3.0.0",
"mitt": "^3.0.1",
"multer": "^1.4.5-lts.1",
"picgo": "^1.5.11",
"picgo": "^1.6.0",
"qrcode.vue": "^3.3.3",
"semver": "^7.7.3",
"shell-path": "2.1.0",
"tunnel": "^0.0.6",
"uuid": "^9.0.0",
"vue": "^3.3.4",
"vue-router": "^4.2.2",
@@ -56,12 +61,14 @@
"devDependencies": {
"@aws-sdk/client-s3": "^3.276.0",
"@aws-sdk/lib-storage": "^3.276.0",
"@eslint/js": "^9.39.1",
"@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",
"@types/inquirer": "^6.5.0",
"@types/js-yaml": "^4.0.5",
"@types/lodash": "^4.17.21",
"@types/lowdb": "^1.0.9",
"@types/multer": "^1.4.12",
"@types/node": "^20",
@@ -70,32 +77,28 @@
"@types/tunnel": "^0.0.3",
"@types/uuid": "^9.0.2",
"@types/write-file-atomic": "^4.0.0",
"@typescript-eslint/eslint-plugin": "^5.48.0",
"@typescript-eslint/parser": "^5.48.0",
"@typescript-eslint/eslint-plugin": "^8.49.0",
"@typescript-eslint/parser": "^8.49.0",
"@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": "^38",
"electron-builder": "26.0.12",
"electron-builder": "26.1.0",
"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",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-vue": "^9.8.0",
"eslint": "^9.39.1",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-promise": "^7.2.1",
"eslint-plugin-vue": "^10.6.2",
"husky": "^3.1.0",
"postcss": "^8.4.23",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
"tailwindcss": "^3.3.2",
"typescript": "^4.4.3",
"typescript": "^5.9.3",
"vite": "^7.2.6"
},
"commitlint": {
+11845
View File
File diff suppressed because it is too large Load Diff
+17
View File
@@ -0,0 +1,17 @@
onlyBuiltDependencies:
- core-js
- ejs
- electron
- electron-winstaller
- esbuild
- husky
- vue-demi
# for multi-arch builds, include both x64 and arm64 versions of electron
# will be deprecated in future(use different arch machine to build different arch binaries)
supportedArchitectures:
os:
- current
cpu:
- x64
- arm64
-1
View File
@@ -1,4 +1,3 @@
const isDevelopment = process.env.NODE_ENV !== 'production'
import { buildRendererUrl } from '~/main/utils/env'
export const TRAY_WINDOW_URL = buildRendererUrl()
+2 -4
View File
@@ -20,9 +20,7 @@ import { DBStore } from '@picgo/store'
import { T } from '~/main/i18n'
import { IRPCActionType } from '~/universal/types/enum'
// Cross-process support may be required in the future
class GuiApi implements IGuiApi {
// eslint-disable-next-line no-use-before-define
private static instance: GuiApi
private windowId: number = -1
private settingWindowId: number = -1
@@ -62,7 +60,7 @@ class GuiApi implements IGuiApi {
await this.showSettingWindow()
this.getWebContentsByWindowId(this.settingWindowId)?.send(SHOW_INPUT_BOX, options)
return new Promise<string>((resolve) => {
ipcMain.once(SHOW_INPUT_BOX, (event: Event, value: string) => {
ipcMain.once(SHOW_INPUT_BOX, (event, value: string) => {
resolve(value)
})
})
@@ -140,7 +138,7 @@ class GuiApi implements IGuiApi {
await this.showSettingWindow()
this.getWebContentsByWindowId(this.settingWindowId)?.send(IRPCActionType.OPEN_CONFIG_DIALOG, options)
return new Promise<T | false>((resolve) => {
ipcMain.once(IRPCActionType.OPEN_CONFIG_DIALOG, (event: Event, value: T | false) => {
ipcMain.once(IRPCActionType.OPEN_CONFIG_DIALOG, (event, value: T | false) => {
resolve(value)
})
})
+3 -3
View File
@@ -24,7 +24,7 @@ function copyFileOutsideOfElectronAsar (
if (fs.existsSync(sourceInAsarArchive)) {
// file will be copied
if (fs.statSync(sourceInAsarArchive).isFile()) {
const file = destOutsideAsarArchive;
const file = destOutsideAsarArchive
const dir = path.dirname(file)
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true })
@@ -35,8 +35,8 @@ function copyFileOutsideOfElectronAsar (
copyFileOutsideOfElectronAsar(
`${sourceInAsarArchive}/${fileOrFolderName}`,
`${destOutsideAsarArchive}/${fileOrFolderName}`
);
});
)
})
}
}
}
-1
View File
@@ -1,4 +1,3 @@
/* eslint-disable camelcase */
import {
TALKING_DATA_APPID, TALKING_DATA_EVENT
} from '~/universal/events/constants'
-1
View File
@@ -4,7 +4,6 @@ import { OPEN_URL, PICGO_OPEN_FILE } from '~/universal/events/constants'
import { webUtils } from 'electron'
const isDevelopment = process.env.NODE_ENV !== 'production'
/* eslint-disable camelcase */
export const handleTalkingDataEvent = (data: ITalkingDataOptions) => {
const { EventId, Label = '', MapKv = {} } = data
MapKv.from = window.location.href
+1 -1
View File
@@ -271,7 +271,7 @@ interface IPrivateShowNotificationOption extends IShowNotificationOption{
interface IShowMessageBoxOption {
title: string
message: string
type: string
type: import('electron').MessageBoxOptions['type']
buttons: string[]
}
+1
View File
@@ -10,6 +10,7 @@
"esModuleInterop": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
-17
View File
@@ -1,17 +0,0 @@
const path = require('path')
function resolve (dir) {
return path.join(__dirname, dir)
}
module.exports = {
configureWebpack: {
devtool: process.env.NODE_ENV === 'development' ? 'source-map' : 'nosources-source-map'
},
chainWebpack: config => {
config.resolve.alias
.set('@', resolve('src/renderer'))
.set('~', resolve('src'))
.set('root', resolve('./'))
.set('#', resolve('src/universal'))
}
}