📦 Chore(multi-arch): add multi-arch build support (#1379)

This commit is contained in:
PiEgg
2026-01-20 14:54:55 +08:00
committed by GitHub
parent 32f501cadb
commit 678cb713a9
7 changed files with 536 additions and 238 deletions
+213 -83
View File
@@ -6,11 +6,6 @@ on:
- v*
workflow_dispatch:
inputs:
test_upload_dist:
description: "Test upload-dist.js script"
required: true
default: false
type: boolean
build_os:
description: "Build for specific OS: Windows, macOS, Linux, All"
required: true
@@ -21,97 +16,243 @@ on:
- macOS
- Linux
- All
test_upload_dist:
description: "Test upload-dist.js script"
required: true
default: false
type: boolean
test_upload_dist_to_dev:
description: "Test upload-dist.js script to dev folder"
required: true
default: false
type: boolean
skip_notarize:
description: "Skip Notarization (true/false)"
required: true
default: false
type: boolean
env:
NODE_VERSION: 22.x
jobs:
# parallel build jobs
build:
name: Build on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
# ============== macOS Builds ==============
build-macos:
name: Build macOS (${{ matrix.arch }})
if: github.event.inputs.build_os == 'macOS' || github.event.inputs.build_os == 'All' || startsWith(github.ref, 'refs/tags/v')
runs-on: ${{ matrix.runner }}
strategy:
# if one job fails, do not stop other jobs
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
include:
- runner: macos-15-intel
target: dmg
arch: x64
- runner: macos-latest
target: dmg
arch: arm64
steps:
- name: Check out git repository
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Clean workspace on Windows
if: runner.os == 'Windows' && (github.event.inputs.build_os == 'Windows' || github.event.inputs.build_os == 'All' || startsWith(github.ref, 'refs/tags/v'))
- name: Clean workspace
run: rm -rf dist dist_electron node_modules ~/.cache/electron-builder ~/.cache/electron
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build macOS App (${{ matrix.arch }})
run: pnpm run build && pnpm exec electron-builder --config electron-builder.config.ts --mac ${{ matrix.target }} --${{ matrix.arch }} --publish never
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
CSC_LINK: ${{ secrets.MAC_CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.MAC_CSC_KEY_PASSWORD }}
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: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: PicGo-macOS-${{ matrix.arch }}
path: dist/*.*
# ============== Windows Builds ==============
build-windows:
name: Build Windows (${{ matrix.arch }})
if: github.event.inputs.build_os == 'Windows' || github.event.inputs.build_os == 'All' || startsWith(github.ref, 'refs/tags/v')
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- runner: windows-latest
arch: x64-ia32
build_arch: --x64 --ia32
target: nsis
- runner: windows-11-arm
arch: arm64
build_arch: --arm64
target: nsis
steps:
- name: Check out git repository
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Clean workspace
run: |
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 }
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' && (github.event.inputs.build_os == 'macOS' || github.event.inputs.build_os == 'Linux' || github.event.inputs.build_os == 'All' || startsWith(github.ref, 'refs/tags/v'))
run: |
rm -rf dist dist_electron node_modules ~/.cache/electron-builder ~/.cache/electron
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Ubuntu Update with sudo
if: runner.os == 'Linux' && (github.event.inputs.build_os == 'Linux' || github.event.inputs.build_os == 'All' || startsWith(github.ref, 'refs/tags/v'))
run: |
sudo apt-get update
sudo apt-get install -y libfuse2
- name: Build Windows x64 & ARM64 App
if: runner.os == 'Windows' && (github.event.inputs.build_os == 'Windows' || github.event.inputs.build_os == 'All' || startsWith(github.ref, 'refs/tags/v'))
run: pnpm run build:win || true
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
- name: Build macOS x64 & ARM64 App
if: runner.os == 'macOS' && (github.event.inputs.build_os == 'macOS' || github.event.inputs.build_os == 'All' || startsWith(github.ref, 'refs/tags/v'))
run: pnpm run build:mac || true
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' || startsWith(github.ref, 'refs/tags/v'))
run: pnpm run build:linux || true
shell: bash
- 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 }}
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: PicGo-${{ runner.os }}
name: PicGo-Windows-${{ matrix.arch }}
path: dist/*.*
- name: Upload to release.picgo.app
if: startsWith(github.ref, 'refs/tags/v') || github.event.inputs.test_upload_dist
# ============== Linux Builds ==============
build-linux:
name: Build Linux (${{ matrix.arch }})
if: github.event.inputs.build_os == 'Linux' || github.event.inputs.build_os == 'All' || startsWith(github.ref, 'refs/tags/v')
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-latest
arch: x64
target: AppImage deb snap
- runner: ubuntu-24.04-arm
arch: arm64
target: AppImage deb
steps:
- name: Check out git repository
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Clean workspace
run: rm -rf dist dist_electron node_modules ~/.cache/electron-builder ~/.cache/electron
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install Linux dependencies
run: |
pnpm run upload-dist
sudo apt-get update
sudo apt-get install -y libfuse2
- name: Build Linux App (${{ matrix.arch }})
run: pnpm run build && pnpm exec electron-builder --config electron-builder.config.ts --linux ${{matrix.target}} --${{ matrix.arch }} --publish never
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: PicGo-Linux-${{ matrix.arch }}
path: dist/*.*
# ============== Release ==============
release:
name: Merge & Release
needs: [build-macos, build-windows, build-linux]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Check out git repository
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: List artifacts
run: ls -laR artifacts/
- name: Merge artifacts and yml files
run: node scripts/merge-artifacts.js
- name: List dist
run: ls -la dist/
- name: Upload to release.picgo.app
if: startsWith(github.ref, 'refs/tags/v') || github.event.inputs.test_upload_dist || github.event.inputs.test_upload_dist_to_dev
run: |
ARGS="--all"
if [[ "${{ github.event.inputs.test_upload_dist_to_dev }}" == "true" ]]; then
ARGS="$ARGS --dev"
echo "🚧 Test Upload Mode: ON"
fi
node scripts/upload-dist.js $ARGS
env:
PICGO_ENV_S3_SECRET_ID: ${{ secrets.PICGO_ENV_S3_SECRET_ID }}
PICGO_ENV_S3_SECRET_KEY: ${{ secrets.PICGO_ENV_S3_SECRET_KEY }}
@@ -119,17 +260,7 @@ jobs:
PICGO_ENV_S3_LEGACY_ACCOUNT_ID: ${{ secrets.PICGO_ENV_S3_LEGACY_ACCOUNT_ID }}
PICGO_ENV_S3_LEGACY_SECRET_ID: ${{ secrets.PICGO_ENV_S3_LEGACY_SECRET_ID }}
PICGO_ENV_S3_LEGACY_SECRET_KEY: ${{ secrets.PICGO_ENV_S3_LEGACY_SECRET_KEY }}
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
@@ -140,16 +271,16 @@ jobs:
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
dist/*.exe
dist/*.dmg
dist/*.zip
dist/*.AppImage
dist/*.deb
dist/*.snap
dist/*.tar.gz
dist/*.yml
dist/*.blockmap
- name: Publish GitHub Release
if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@v2
@@ -160,13 +291,12 @@ jobs:
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
dist/*.exe
dist/*.dmg
dist/*.zip
dist/*.AppImage
dist/*.deb
dist/*.snap
dist/*.tar.gz
dist/*.yml
dist/*.blockmap
+1 -1
View File
@@ -3,7 +3,7 @@ import dotenv from 'dotenv'
dotenv.config()
const shouldNotarize = process.env.SKIP_NOTARIZE !== 'true';
const shouldNotarize = process.env.SKIP_NOTARIZE !== 'true'
const config: Configuration = {
appId: 'com.molunerfinn.picgo',
+3 -3
View File
@@ -9,7 +9,7 @@
"email": "marksz@teamsz.xyz"
},
"scripts": {
"build": "electron-vite build && electron-builder --config electron-builder.config.ts --publish never",
"build": "electron-vite build",
"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",
@@ -44,7 +44,7 @@
"element-plus": "^2.3.7",
"epipebomb": "^1.0.0",
"fs-extra": "^10.0.0",
"js-yaml": "^4.1.0",
"js-yaml": "^4.1.1",
"keycode": "^2.2.0",
"lodash": "^4.17.21",
"lodash-id": "^0.14.0",
@@ -132,4 +132,4 @@
"commit-msg": "npm run lint:dpdm && commitlint -E HUSKY_GIT_PARAMS"
}
}
}
}
+1 -1
View File
@@ -45,7 +45,7 @@ importers:
specifier: ^10.0.0
version: 10.1.0
js-yaml:
specifier: ^4.1.0
specifier: ^4.1.1
version: 4.1.1
keycode:
specifier: ^2.2.0
+3 -3
View File
@@ -16,15 +16,15 @@ const macos = [
const linux = {
AppImage: [
{ label: '64 bit', arch: '-x64', ext: '.AppImage' },
{ label: '64 bit', arch: '-amd64', ext: '.AppImage' },
{ label: 'ARM64', arch: '-arm64', ext: '.AppImage' }
],
Deb: [
{ label: '64 bit', arch: '-x64', ext: '.deb' },
{ label: '64 bit', arch: '-amd64', ext: '.deb' },
{ label: 'ARM64', arch: '-arm64', ext: '.deb' }
],
Snap: [
{ label: '64 bit', arch: '-x64', ext: '.snap' }
{ label: '64 bit', arch: '-amd64', ext: '.snap' }
]
}
+197
View File
@@ -0,0 +1,197 @@
/**
* Merge artifacts from different platforms and architectures
* Also merge latest*.yml files for electron-updater
*/
const fs = require('fs')
const path = require('path')
const yaml = require('js-yaml')
const ARTIFACTS_DIR = path.join(__dirname, '../artifacts')
const DIST_DIR = path.join(__dirname, '../dist')
// yml 文件分组规则
const YML_MERGE_RULES = {
// macOS: 合并 x64 和 arm64 的 latest-mac.yml
'latest-mac.yml': ['latest-mac.yml'],
// Windows: 合并所有架构的 latest.yml
'latest.yml': ['latest.yml'],
// Linux x64: latest-linux.yml
'latest-linux.yml': ['latest-linux.yml'],
// Linux arm64: latest-linux-arm64.yml
'latest-linux-arm64.yml': ['latest-linux-arm64.yml']
}
/**
* 递归查找指定文件名的所有文件
*/
function findFiles(dir, filename) {
const results = []
if (!fs.existsSync(dir)) {
return results
}
const items = fs.readdirSync(dir)
for (const item of items) {
const fullPath = path.join(dir, item)
const stat = fs.statSync(fullPath)
if (stat.isDirectory()) {
results.push(...findFiles(fullPath, filename))
} else if (item === filename) {
results.push(fullPath)
}
}
return results
}
/**
* 合并多个 yml 文件
*/
function mergeYmlFiles(files) {
if (files.length === 0) return null
if (files.length === 1) {
return yaml.load(fs.readFileSync(files[0], 'utf8'))
}
const contents = files.map(f => yaml.load(fs.readFileSync(f, 'utf8')))
// 以第一个为基准,合并 files 数组
const merged = {
version: contents[0].version,
files: [],
releaseDate: contents[0].releaseDate
}
for (const content of contents) {
if (content.files && Array.isArray(content.files)) {
merged.files.push(...content.files)
}
}
// 去重(根据 sha512
const seen = new Set()
merged.files = merged.files.filter(file => {
const key = file.sha512
if (seen.has(key)) return false
seen.add(key)
return true
})
// 设置 path/sha512/size 为第一个文件(electron-updater 兼容性)
if (merged.files.length > 0) {
merged.path = merged.files[0].url
merged.sha512 = merged.files[0].sha512
merged.size = merged.files[0].size
}
return merged
}
/**
* 复制所有构建产物到 dist 目录
*/
function copyArtifacts() {
console.log('📁 Copying all artifacts to dist...\n')
if (!fs.existsSync(ARTIFACTS_DIR)) {
console.log('⚠️ No artifacts directory found')
return
}
const platformDirs = fs.readdirSync(ARTIFACTS_DIR)
for (const platformDir of platformDirs) {
const platformPath = path.join(ARTIFACTS_DIR, platformDir)
const stat = fs.statSync(platformPath)
if (!stat.isDirectory()) continue
console.log(`📦 Processing ${platformDir}...`)
const files = fs.readdirSync(platformPath)
for (const file of files) {
const srcPath = path.join(platformPath, file)
const destPath = path.join(DIST_DIR, file)
const fileStat = fs.statSync(srcPath)
// 跳过目录和 yml 文件(yml 文件会单独处理合并)
if (fileStat.isDirectory()) continue
if (file.endsWith('.yml')) continue
// 如果目标文件已存在且大小相同,跳过
if (fs.existsSync(destPath)) {
const destStat = fs.statSync(destPath)
if (destStat.size === fileStat.size) {
console.log(` ⏭️ Skipped (exists): ${file}`)
continue
}
}
fs.copyFileSync(srcPath, destPath)
console.log(` ✅ Copied: ${file}`)
}
}
}
/**
* 合并 yml 文件
*/
function mergeYmlFilesFromArtifacts() {
console.log('\n🔀 Merging yml files...\n')
for (const [outputName, sourceNames] of Object.entries(YML_MERGE_RULES)) {
const allFiles = []
for (const sourceName of sourceNames) {
const files = findFiles(ARTIFACTS_DIR, sourceName)
allFiles.push(...files)
}
if (allFiles.length === 0) {
console.log(`⏭️ No ${outputName} found, skipping...`)
continue
}
console.log(`📄 Found ${allFiles.length} ${outputName} file(s):`)
allFiles.forEach(f => console.log(` - ${path.relative(ARTIFACTS_DIR, f)}`))
const merged = mergeYmlFiles(allFiles)
if (merged) {
const outputPath = path.join(DIST_DIR, outputName)
fs.writeFileSync(outputPath, yaml.dump(merged, { lineWidth: -1 }))
console.log(`✅ Merged -> ${outputName}`)
if (merged.files) {
console.log(` Files: ${merged.files.map(f => f.url).join(', ')}`)
}
console.log('')
}
}
}
async function main() {
console.log('🚀 Starting artifact merge process...\n')
// 确保 dist 目录存在
if (!fs.existsSync(DIST_DIR)) {
fs.mkdirSync(DIST_DIR, { recursive: true })
}
// 1. 复制所有构建产物
copyArtifacts()
// 2. 合并 yml 文件
mergeYmlFilesFromArtifacts()
console.log('🎉 Artifact merge completed!')
}
main().catch(err => {
console.error('❌ Error:', err)
process.exit(1)
})
+118 -147
View File
@@ -2,8 +2,6 @@
// upload version file to cos
require('dotenv').config()
// const crypto = require('crypto')
// const axios = require('axios').default
const fs = require('fs')
const pkg = require('../package.json')
const configList = require('./config')
@@ -12,15 +10,13 @@ const path = require('path')
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 uploadToDev = process.argv.includes('--dev')
const S3_BUCKET = 'release'
const S3_LEGACY_BUCKET = 'picgo'
// const AREA = 'ap-chengdu'
const VERSION = pkg.version
const FILE_PATH = `${VERSION}/`
const DEV_DIST_PREFIX = 'dev/'
const FILE_PATH = uploadToDev ? `${DEV_DIST_PREFIX}${VERSION}/` : `${VERSION}/`
const S3_SECRET_ID = process.env.PICGO_ENV_S3_SECRET_ID
const S3_SECRET_KEY = process.env.PICGO_ENV_S3_SECRET_KEY
const S3_ACCOUNT_ID = process.env.PICGO_ENV_S3_ACCOUNT_ID
@@ -49,155 +45,130 @@ const S3LegacyOptions = {
region: 'auto'
}
// https://cloud.tencent.com/document/product/436/7778#signature
// /**
// * @param {string} fileName
// * @returns
// */
// const generateSignature = (fileName, folder = FILE_PATH) => {
// const secretKey = COS_SECRET_ID
// // const area = AREA
// const bucket = BUCKET
// const path = folder
// const today = Math.floor(new Date().getTime() / 1000)
// const tomorrow = today + 86400
// const signTime = `${today};${tomorrow}`
// const signKey = crypto.createHmac('sha1', secretKey).update(signTime).digest('hex')
// const httpString = `put\n/${path}${fileName}\n\nhost=${bucket}.cos.accelerate.myqcloud.com\n`
// const sha1edHttpString = crypto.createHash('sha1').update(httpString).digest('hex')
// const stringToSign = `sha1\n${signTime}\n${sha1edHttpString}\n`
// const signature = crypto.createHmac('sha1', signKey).update(stringToSign).digest('hex')
// return {
// signature,
// signTime
// }
// }
// /**
// *
// * @param {string} fileName
// * @param {Buffer} fileBuffer
// * @param {{ signature: string, signTime: string }} signature
// * @returns
// */
// const getReqOptions = (fileName, fileBuffer, signature, folder = FILE_PATH) => {
// return {
// method: 'PUT',
// url: `http://${BUCKET}.cos.accelerate.myqcloud.com/${encodeURI(folder)}${encodeURI(fileName)}`,
// headers: {
// Host: `${BUCKET}.cos.accelerate.myqcloud.com`,
// Authorization: `q-sign-algorithm=sha1&q-ak=${COS_SECRET_KEY}&q-sign-time=${signature.signTime}&q-key-time=${signature.signTime}&q-header-list=host&q-url-param-list=&q-signature=${signature.signature}`,
// contentType: mime.lookup(fileName),
// useAgent: `PicGo;${pkg.version};null;null`
// },
// maxContentLength: Infinity,
// maxBodyLength: Infinity,
// data: fileBuffer,
// resolveWithFullResponse: true
// }
// }
/**
* 检查是否使用 --all 参数(上传所有平台)
*/
function shouldUploadAll() {
return process.argv.includes('--all')
}
/**
* a backup for version file
* 获取要上传的配置列表
*/
// const uploadVersionFile = async () => {
// try {
// const platform = process.platform
// if (configList[platform]) {
// let versionFileHasUploaded = false
// for (const [, config] of configList[platform].entries()) {
// const versionFilePath = path.join(distPath, config['version-file'])
// let versionFileName = config['version-file']
// if (VERSION.toLocaleLowerCase().includes('beta')) {
// versionFileName = versionFileName.replace('.yml', '.beta.yml')
// }
// // upload version file
// if (!versionFileHasUploaded) {
// const signature = generateSignature(versionFileName, '')
// const reqOptions = getReqOptions(versionFileName, fs.readFileSync(versionFilePath), signature, '')
// console.log('[PicGo Version File] Uploading...', versionFileName)
// await axios.request(reqOptions)
// versionFileHasUploaded = true
// }
// }
// } else {
// console.warn('platform not supported!', platform)
// }
// } catch (e) {
// console.error(e)
// }
// }
function getUploadConfigs() {
if (shouldUploadAll()) {
// 合并所有平台的配置
return [
...configList.darwin,
...configList.win32,
...configList.linux
]
}
// 原有逻辑:根据当前平台
const platform = process.platform
return configList[platform] || []
}
/**
* 上传单个文件到 S3
*/
async function uploadFileToS3(client, bucket, key, filePath, contentType = 'application/octet-stream') {
const upload = new Upload({
client,
params: {
Bucket: bucket,
Key: key,
Body: fs.createReadStream(filePath),
ContentType: contentType
}
})
upload.on('httpUploadProgress', progress => {
const percent = progress.total ? Math.round((progress.loaded / progress.total) * 100) : 0
process.stdout.write(`\r Progress: ${progress.loaded}/${progress.total || '?'} (${percent}%)`)
})
await upload.done()
console.log('') // 换行
}
const uploadDist = async () => {
try {
const platform = process.platform
if (configList[platform]) {
const uploadedVersionFiles = new Set()
for (const [index, config] of configList[platform].entries()) {
const fileName = `${config.appNameWithPrefix}-${VERSION}-${config.arch}.${config.ext}`
const filePath = path.join(distPath, fileName)
const versionFilePath = path.join(distPath, config['version-file'])
let versionFileName = config['version-file']
if (VERSION.toLocaleLowerCase().includes('beta')) {
versionFileName = versionFileName.replace('.yml', '.beta.yml')
}
console.log('[PicGo Dist] Preparing to upload', fileName)
const client = new S3Client(S3Options)
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()
} else {
console.warn('[PicGo Dist] File not found:', fileName)
}
const configs = getUploadConfigs()
// upload version file
if (!uploadedVersionFiles.has(versionFileName) && fs.existsSync(versionFilePath)) {
const uploadVersionFileToS3 = new Upload({
client,
params: {
Bucket: S3_BUCKET,
Key: `${versionFileName}`,
Body: fs.createReadStream(versionFilePath),
ContentType: mime.lookup(versionFileName)
}
})
// upload to legacy bucket as well
// will be deprecated in 2.5.0
const legacyClient = new S3Client(S3LegacyOptions)
const uploadVersionFileToLegacyS3 = new Upload({
client: legacyClient,
params: {
Bucket: S3_LEGACY_BUCKET,
Key: `${versionFileName}`,
Body: fs.createReadStream(versionFilePath),
ContentType: mime.lookup(versionFileName)
}
})
console.log('[PicGo Version File] Uploading...', versionFileName)
await uploadVersionFileToS3.done()
await uploadVersionFileToLegacyS3.done()
uploadedVersionFiles.add(versionFileName)
console.log('[PicGo Version File] Upload successfully')
}
}
} else {
console.warn('platform not supported!', platform)
if (configs.length === 0) {
console.warn('[PicGo] No upload config found!')
return
}
console.log(`[PicGo] Upload mode: ${shouldUploadAll() ? 'ALL PLATFORMS' : process.platform}`)
console.log(`[PicGo] Version: ${VERSION}`)
console.log(`[PicGo] Total files to upload: ${configs.length}\n`)
const uploadedVersionFiles = new Set()
const client = new S3Client(S3Options)
const legacyClient = new S3Client(S3LegacyOptions)
for (const [index, config] of configs.entries()) {
const fileName = `${config.appNameWithPrefix}-${VERSION}-${config.arch}.${config.ext}`
const filePath = path.join(distPath, fileName)
let versionFileName = config['version-file']
console.log(`[${index + 1}/${configs.length}] Processing ${fileName}`)
// 上传构建产物
if (fs.existsSync(filePath)) {
console.log(` Uploading to S3: ${FILE_PATH}${fileName}`)
await uploadFileToS3(client, S3_BUCKET, `${FILE_PATH}${fileName}`, filePath)
console.log(` ✅ Uploaded: ${fileName}`)
} else {
console.warn(` ⚠️ File not found: ${fileName}`)
}
let versionFilePath = path.join(distPath, versionFileName)
// Beta 版本使用不同的 yml 文件名
if (VERSION.toLowerCase().includes('beta') && fs.existsSync(versionFilePath)) {
versionFileName = versionFileName.replace('.yml', '.beta.yml')
const betaVersionFilePath = path.join(distPath, versionFileName)
// change to beta version file path
fs.renameSync(versionFilePath, betaVersionFilePath)
versionFilePath = betaVersionFilePath
}
// 上传版本文件(每个 yml 只上传一次)
if (!uploadedVersionFiles.has(versionFileName) && fs.existsSync(versionFilePath)) {
console.log(` Uploading version file: ${versionFileName}`)
const versionFileNameFinal = uploadToDev ? `${DEV_DIST_PREFIX}${versionFileName}` : versionFileName
// 上传到主 bucket
await uploadFileToS3(
client,
S3_BUCKET,
versionFileNameFinal,
versionFilePath,
mime.lookup(versionFileName) || 'text/yaml'
)
// 上传到 legacy bucket
await uploadFileToS3(
legacyClient,
S3_LEGACY_BUCKET,
versionFileNameFinal,
versionFilePath,
mime.lookup(versionFileName) || 'text/yaml'
)
uploadedVersionFiles.add(versionFileName)
console.log(` ✅ Version file uploaded: ${versionFileName}`)
}
console.log('')
}
console.log('[PicGo] 🎉 All uploads completed!')
} catch (e) {
console.error(e)
console.error('[PicGo] ❌ Upload error:', e)
process.exit(1)
}
}