diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 053ea72..24dacd0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,6 +21,11 @@ on: - macOS - Linux - All + skip_notarize: + description: "Skip Notarization (true/false)" + required: false + default: false + type: boolean env: NODE_VERSION: 22.x @@ -82,6 +87,16 @@ jobs: shell: bash env: GH_TOKEN: ${{ secrets.GH_TOKEN }} + # macOS Code Signing + # p12 证书的 Base64 字符串 + CSC_LINK: ${{ secrets.MAC_CSC_LINK }} + # p12 证书密码 + CSC_KEY_PASSWORD: ${{ secrets.MAC_CSC_KEY_PASSWORD }} + # macOS Notarization (公证所需变量) + 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 }} - name: Build Linux x64 & ARM64 App if: runner.os == 'Linux' && (github.event.inputs.build_os == 'Linux' || github.event.inputs.build_os == 'All') run: pnpm run build:linux || true diff --git a/.gitignore b/.gitignore index 1d5f097..be095fb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ dist/web/* build/* !build/icons !build/installer.nsh +!build/entitlements.mac.plist coverage node_modules/ npm-debug.log diff --git a/build/entitlements.mac.plist b/build/entitlements.mac.plist new file mode 100644 index 0000000..14c9a7b --- /dev/null +++ b/build/entitlements.mac.plist @@ -0,0 +1,17 @@ + + + + + + com.apple.security.cs.allow-jit + + + + com.apple.security.cs.disable-library-validation + + + + com.apple.security.cs.allow-unsigned-executable-memory + + + diff --git a/electron-builder.config.ts b/electron-builder.config.ts index 25b0c8c..d96304a 100644 --- a/electron-builder.config.ts +++ b/electron-builder.config.ts @@ -1,9 +1,14 @@ - import type { Configuration } from 'electron-builder' +import dotenv from 'dotenv' + +dotenv.config() + +const shouldNotarize = process.env.SKIP_NOTARIZE !== 'true'; const config: Configuration = { appId: 'com.molunerfinn.picgo', productName: 'PicGo', + afterSign: shouldNotarize ? 'scripts/notarize.js' : undefined, // publish: [ // { // provider: 'github', @@ -53,7 +58,11 @@ const config: Configuration = { arch: ['x64', 'arm64'] } ], - artifactName: 'PicGo-${version}-${arch}.${ext}' + artifactName: 'PicGo-${version}-${arch}.${ext}', + hardenedRuntime: true, + entitlements: 'build/entitlements.mac.plist', + entitlementsInherit: 'build/entitlements.mac.plist', + notarize: false }, win: { icon: 'build/icons/icon.ico', diff --git a/eslint.config.js b/eslint.config.js index 34b692b..44a16d5 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,3 +1,4 @@ +const globals = require('globals') const path = require('node:path') const eslintJs = require('@eslint/js') const tsPlugin = require('@typescript-eslint/eslint-plugin') @@ -122,5 +123,16 @@ module.exports = [ '@typescript-eslint/no-empty-object-type': 'off', '@typescript-eslint/no-explicit-any': 'off' } - } + }, + { + // 1. 针对所有 JS 文件(或者特定目录)启用 Node 全局变量 + files: ["**/*.js", "scripts/*.js"], + languageOptions: { + globals: { + ...globals.node, // 注入 process, require, module, __dirname 等 + ...globals.browser // 如果你的项目是前端项目,可能还需要 browser + }, + sourceType: "commonjs" // 如果你的项目代码主要是 CJS,加上这个;如果是 ESM 则设为 "module" + } + }, ] diff --git a/package.json b/package.json index 38dd491..accc935 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "build:win": "npm run build && electron-builder --config electron-builder.config.ts --win --publish never", "build:mac": "npm run build && electron-builder --config electron-builder.config.ts --mac --publish never", "build:linux": "npm run build && electron-builder --config electron-builder.config.ts --linux --publish never", + "build:local": "dotenv -e .env -- electron-vite build && electron-builder --config electron-builder.config.ts --publish never", "lint": "eslint --ext .js,.jsx,.ts,.tsx,.vue src/", "tsc": "tsc --noEmit", "bump": "bump-version", @@ -88,6 +89,7 @@ "conventional-changelog": "^3.1.18", "cz-customizable": "^7.5.1", "dotenv": "^16.0.1", + "dotenv-cli": "^11.0.0", "dpdm": "^3.13.1", "electron": "^38", "electron-builder": "26.1.0", @@ -97,6 +99,7 @@ "eslint-plugin-import": "^2.32.0", "eslint-plugin-promise": "^7.2.1", "eslint-plugin-vue": "^10.6.2", + "globals": "^16.5.0", "husky": "^3.1.0", "postcss": "^8.4.23", "stylus": "^0.54.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a58e1ed..579d587 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -186,6 +186,9 @@ importers: dotenv: specifier: ^16.0.1 version: 16.6.1 + dotenv-cli: + specifier: ^11.0.0 + version: 11.0.0 dpdm: specifier: ^3.13.1 version: 3.14.0 @@ -213,6 +216,9 @@ importers: eslint-plugin-vue: specifier: ^10.6.2 version: 10.6.2(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@1.21.7))) + globals: + specifier: ^16.5.0 + version: 16.5.0 husky: specifier: ^3.1.0 version: 3.1.0 @@ -2607,14 +2613,26 @@ packages: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} + dotenv-cli@11.0.0: + resolution: {integrity: sha512-r5pA8idbk7GFWuHEU7trSTflWcdBpQEK+Aw17UrSHjS6CReuhrrPcyC3zcQBPQvhArRHnBo/h6eLH1fkCvNlww==} + hasBin: true + dotenv-expand@11.0.7: resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} engines: {node: '>=12'} + dotenv-expand@12.0.3: + resolution: {integrity: sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==} + engines: {node: '>=12'} + dotenv@16.6.1: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} + dotenv@17.2.3: + resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} + engines: {node: '>=12'} + download-git-repo@3.0.2: resolution: {integrity: sha512-N8hWXD4hXqmEcNoR8TBYFntaOcYvEQ7Bz90mgm3bZRTuteGQqwT32VDMnTyD0KTEvb8BWrMc1tVmzuV9u/WrAg==} @@ -3274,6 +3292,10 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: '>=18'} + globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -8769,12 +8791,25 @@ snapshots: dependencies: is-obj: 2.0.0 + dotenv-cli@11.0.0: + dependencies: + cross-spawn: 7.0.6 + dotenv: 17.2.3 + dotenv-expand: 12.0.3 + minimist: 1.2.8 + dotenv-expand@11.0.7: dependencies: dotenv: 16.6.1 + dotenv-expand@12.0.3: + dependencies: + dotenv: 16.6.1 + dotenv@16.6.1: {} + dotenv@17.2.3: {} + download-git-repo@3.0.2: dependencies: download: 7.1.0 @@ -9665,6 +9700,8 @@ snapshots: globals@14.0.0: {} + globals@16.5.0: {} + globalthis@1.0.4: dependencies: define-properties: 1.2.1 diff --git a/scripts/notarize.js b/scripts/notarize.js new file mode 100644 index 0000000..20084a6 --- /dev/null +++ b/scripts/notarize.js @@ -0,0 +1,39 @@ +require('dotenv').config() + +const { notarize } = require('@electron/notarize') +const { APPLE_ID, APPLE_TEAM_ID, APPLE_APP_SPECIFIC_PASSWORD } = process.env +const APP_BUNDLE_ID = 'com.molunerfinn.picgo' + +async function main(context) { + const { electronPlatformName, appOutDir, packager } = context + + if ( + electronPlatformName !== 'darwin' || + !APPLE_ID || + !APPLE_APP_SPECIFIC_PASSWORD || + !APPLE_TEAM_ID + ) { + console.log('Skip notarization.') + return + } + + const appName = packager.appInfo.productFilename + const appPath = `${appOutDir}/${appName}.app` + + const now = Date.now() + + console.log('Starting Apple notarization for', appPath) + + await notarize({ + appPath, + appBundleId: APP_BUNDLE_ID, + appleId: APPLE_ID, + appleIdPassword: APPLE_APP_SPECIFIC_PASSWORD, + teamId: APPLE_TEAM_ID + }) + + console.log('Finished Apple notarization for', appPath, `in ${(Date.now() - now) / 1000}s`) +} + + +module.exports = main