2026-01-10 23:15:42 +08:00
|
|
|
import dotenv from 'dotenv';
|
|
|
|
|
import fs from 'node:fs/promises';
|
|
|
|
|
import os from 'node:os';
|
|
|
|
|
import path from 'node:path';
|
|
|
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
|
|
|
|
|
|
import { getAsarUnpackPatterns, getFilesPatterns } from './native-deps.config.mjs';
|
2025-04-27 11:57:06 +08:00
|
|
|
|
|
|
|
|
dotenv.config();
|
|
|
|
|
|
2026-01-10 23:15:42 +08:00
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
|
|
2026-01-15 17:26:19 +08:00
|
|
|
const packageJSON = JSON.parse(await fs.readFile(path.join(__dirname, 'package.json'), 'utf8'));
|
2025-04-27 11:57:06 +08:00
|
|
|
|
2025-04-27 19:58:00 +08:00
|
|
|
const channel = process.env.UPDATE_CHANNEL;
|
2025-09-08 23:46:57 +08:00
|
|
|
const arch = os.arch();
|
2025-09-24 12:38:58 +08:00
|
|
|
const hasAppleCertificate = Boolean(process.env.CSC_LINK);
|
2025-04-27 11:57:06 +08:00
|
|
|
|
2026-01-15 17:26:19 +08:00
|
|
|
// 自定义更新服务器 URL (用于 stable 频道)
|
|
|
|
|
const updateServerUrl = process.env.UPDATE_SERVER_URL;
|
|
|
|
|
|
2025-04-27 11:57:06 +08:00
|
|
|
console.log(`🚄 Build Version ${packageJSON.version}, Channel: ${channel}`);
|
2025-09-08 23:46:57 +08:00
|
|
|
console.log(`🏗️ Building for architecture: ${arch}`);
|
2025-04-27 11:57:06 +08:00
|
|
|
|
|
|
|
|
const isNightly = channel === 'nightly';
|
2025-05-11 10:57:31 +08:00
|
|
|
const isBeta = packageJSON.name.includes('beta');
|
2026-01-15 17:26:19 +08:00
|
|
|
const isStable = !isNightly && !isBeta;
|
|
|
|
|
|
|
|
|
|
// 根据 channel 配置不同的 publish provider
|
|
|
|
|
// - Stable + UPDATE_SERVER_URL: 使用 generic (自定义 HTTP 服务器)
|
|
|
|
|
// - Beta/Nightly: 仅使用 GitHub
|
|
|
|
|
const getPublishConfig = () => {
|
|
|
|
|
const githubProvider = {
|
|
|
|
|
owner: 'lobehub',
|
|
|
|
|
provider: 'github',
|
|
|
|
|
repo: 'lobe-chat',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Stable channel: 使用自定义服务器 (generic provider)
|
|
|
|
|
if (isStable && updateServerUrl) {
|
|
|
|
|
console.log(`📦 Stable channel: Using generic provider (${updateServerUrl})`);
|
|
|
|
|
const genericProvider = {
|
|
|
|
|
provider: 'generic',
|
|
|
|
|
url: updateServerUrl,
|
|
|
|
|
};
|
|
|
|
|
// 同时发布到自定义服务器和 GitHub (GitHub 作为备用/镜像)
|
|
|
|
|
return [genericProvider, githubProvider];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Beta/Nightly channel: 仅使用 GitHub
|
|
|
|
|
console.log(`📦 ${channel || 'default'} channel: Using GitHub provider`);
|
|
|
|
|
return [githubProvider];
|
|
|
|
|
};
|
2025-05-11 10:57:31 +08:00
|
|
|
|
2025-12-19 23:09:24 +08:00
|
|
|
// Keep only these Electron Framework localization folders (*.lproj)
|
|
|
|
|
// (aligned with previous Electron Forge build config)
|
|
|
|
|
const keepLanguages = new Set(['en', 'en_GB', 'en-US', 'en_US']);
|
|
|
|
|
|
2025-09-24 12:38:58 +08:00
|
|
|
// https://www.electron.build/code-signing-mac#how-to-disable-code-signing-during-the-build-process-on-macos
|
2025-09-23 10:54:36 +08:00
|
|
|
if (!hasAppleCertificate) {
|
|
|
|
|
// Disable auto discovery to keep electron-builder from searching unavailable signing identities
|
|
|
|
|
process.env.CSC_IDENTITY_AUTO_DISCOVERY = 'false';
|
2025-09-24 12:38:58 +08:00
|
|
|
console.log('⚠️ Apple certificate link not found, macOS artifacts will be unsigned.');
|
2025-09-23 10:54:36 +08:00
|
|
|
}
|
|
|
|
|
|
2025-08-06 13:23:27 +08:00
|
|
|
// 根据版本类型确定协议 scheme
|
|
|
|
|
const getProtocolScheme = () => {
|
|
|
|
|
if (isNightly) return 'lobehub-nightly';
|
|
|
|
|
if (isBeta) return 'lobehub-beta';
|
|
|
|
|
|
|
|
|
|
return 'lobehub';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const protocolScheme = getProtocolScheme();
|
|
|
|
|
|
2025-10-08 23:30:35 +08:00
|
|
|
// Determine icon file based on version type
|
|
|
|
|
const getIconFileName = () => {
|
|
|
|
|
if (isNightly) return 'Icon-nightly';
|
|
|
|
|
if (isBeta) return 'Icon-beta';
|
|
|
|
|
return 'Icon';
|
|
|
|
|
};
|
|
|
|
|
|
2025-04-27 11:57:06 +08:00
|
|
|
/**
|
|
|
|
|
* @type {import('electron-builder').Configuration}
|
|
|
|
|
* @see https://www.electron.build/configuration
|
|
|
|
|
*/
|
|
|
|
|
const config = {
|
2025-10-08 23:30:35 +08:00
|
|
|
/**
|
|
|
|
|
* AfterPack hook to copy pre-generated Liquid Glass Assets.car for macOS 26+
|
|
|
|
|
* @see https://github.com/electron-userland/electron-builder/issues/9254
|
|
|
|
|
* @see https://github.com/MultiboxLabs/flow-browser/pull/159
|
|
|
|
|
* @see https://github.com/electron/packager/pull/1806
|
|
|
|
|
*/
|
|
|
|
|
afterPack: async (context) => {
|
|
|
|
|
// Only process macOS builds
|
2025-12-19 23:09:24 +08:00
|
|
|
if (!['darwin', 'mas'].includes(context.electronPlatformName)) {
|
2025-10-08 23:30:35 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const iconFileName = getIconFileName();
|
|
|
|
|
const assetsCarSource = path.join(__dirname, 'build', `${iconFileName}.Assets.car`);
|
|
|
|
|
const resourcesPath = path.join(
|
|
|
|
|
context.appOutDir,
|
|
|
|
|
`${context.packager.appInfo.productFilename}.app`,
|
|
|
|
|
'Contents',
|
|
|
|
|
'Resources',
|
|
|
|
|
);
|
|
|
|
|
const assetsCarDest = path.join(resourcesPath, 'Assets.car');
|
|
|
|
|
|
2025-12-19 23:09:24 +08:00
|
|
|
// Remove unused Electron Framework localizations to reduce app size
|
|
|
|
|
// Equivalent to:
|
|
|
|
|
// ../../Frameworks/Electron Framework.framework/Versions/A/Resources/*.lproj
|
|
|
|
|
const frameworkResourcePath = path.join(
|
|
|
|
|
context.appOutDir,
|
|
|
|
|
`${context.packager.appInfo.productFilename}.app`,
|
|
|
|
|
'Contents',
|
|
|
|
|
'Frameworks',
|
|
|
|
|
'Electron Framework.framework',
|
|
|
|
|
'Versions',
|
|
|
|
|
'A',
|
|
|
|
|
'Resources',
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const entries = await fs.readdir(frameworkResourcePath);
|
|
|
|
|
await Promise.all(
|
|
|
|
|
entries.map(async (file) => {
|
|
|
|
|
if (!file.endsWith('.lproj')) return;
|
|
|
|
|
|
|
|
|
|
const lang = file.split('.')[0];
|
|
|
|
|
if (keepLanguages.has(lang)) return;
|
|
|
|
|
|
|
|
|
|
await fs.rm(path.join(frameworkResourcePath, file), { force: true, recursive: true });
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
} catch {
|
|
|
|
|
// Non-critical: folder may not exist depending on packaging details
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-08 23:30:35 +08:00
|
|
|
try {
|
|
|
|
|
await fs.access(assetsCarSource);
|
|
|
|
|
await fs.copyFile(assetsCarSource, assetsCarDest);
|
|
|
|
|
console.log(`✅ Copied Liquid Glass icon: ${iconFileName}.Assets.car`);
|
|
|
|
|
} catch {
|
|
|
|
|
// Non-critical: Assets.car not found or copy failed
|
|
|
|
|
// App will use fallback .icns icon on all macOS versions
|
|
|
|
|
console.log(`⏭️ Skipping Assets.car (not found or copy failed)`);
|
|
|
|
|
}
|
|
|
|
|
},
|
2025-04-27 19:58:00 +08:00
|
|
|
appId: isNightly
|
|
|
|
|
? 'com.lobehub.lobehub-desktop-nightly'
|
|
|
|
|
: isBeta
|
|
|
|
|
? 'com.lobehub.lobehub-desktop-beta'
|
|
|
|
|
: 'com.lobehub.lobehub-desktop',
|
2025-04-27 11:57:06 +08:00
|
|
|
appImage: {
|
|
|
|
|
artifactName: '${productName}-${version}.${ext}',
|
|
|
|
|
},
|
|
|
|
|
asar: true,
|
2026-01-10 23:15:42 +08:00
|
|
|
// Native modules must be unpacked from asar to work correctly
|
|
|
|
|
asarUnpack: getAsarUnpackPatterns(),
|
|
|
|
|
|
2025-04-27 11:57:06 +08:00
|
|
|
detectUpdateChannel: true,
|
2026-01-10 23:15:42 +08:00
|
|
|
|
2025-04-27 11:57:06 +08:00
|
|
|
directories: {
|
|
|
|
|
buildResources: 'build',
|
|
|
|
|
output: 'release',
|
|
|
|
|
},
|
2026-01-10 23:15:42 +08:00
|
|
|
|
2025-04-27 11:57:06 +08:00
|
|
|
dmg: {
|
|
|
|
|
artifactName: '${productName}-${version}-${arch}.${ext}',
|
|
|
|
|
},
|
2026-01-10 23:15:42 +08:00
|
|
|
|
2025-04-27 11:57:06 +08:00
|
|
|
electronDownload: {
|
|
|
|
|
mirror: 'https://npmmirror.com/mirrors/electron/',
|
|
|
|
|
},
|
2026-01-10 23:15:42 +08:00
|
|
|
|
2025-04-27 11:57:06 +08:00
|
|
|
files: [
|
|
|
|
|
'dist',
|
|
|
|
|
'resources',
|
2025-12-19 23:09:24 +08:00
|
|
|
// Ensure Next export assets are packaged
|
|
|
|
|
'dist/next/**/*',
|
2025-04-27 11:57:06 +08:00
|
|
|
'!resources/locales',
|
|
|
|
|
'!dist/next/docs',
|
|
|
|
|
'!dist/next/packages',
|
|
|
|
|
'!dist/next/.next/server/app/sitemap',
|
|
|
|
|
'!dist/next/.next/static/media',
|
2026-01-10 23:15:42 +08:00
|
|
|
// Exclude node_modules from packaging (except native modules)
|
|
|
|
|
'!node_modules',
|
|
|
|
|
// Include native modules (defined in native-deps.config.mjs)
|
|
|
|
|
...getFilesPatterns(),
|
2025-04-27 11:57:06 +08:00
|
|
|
],
|
|
|
|
|
generateUpdatesFilesForAllChannels: true,
|
|
|
|
|
linux: {
|
|
|
|
|
category: 'Utility',
|
|
|
|
|
maintainer: 'electronjs.org',
|
2025-08-21 23:40:05 +08:00
|
|
|
target: ['AppImage', 'snap', 'deb', 'rpm', 'tar.gz'],
|
2025-04-27 11:57:06 +08:00
|
|
|
},
|
|
|
|
|
mac: {
|
|
|
|
|
compression: 'maximum',
|
|
|
|
|
entitlementsInherit: 'build/entitlements.mac.plist',
|
2025-05-11 10:57:31 +08:00
|
|
|
extendInfo: {
|
2025-10-08 23:30:35 +08:00
|
|
|
CFBundleIconName: 'AppIcon',
|
2025-08-06 13:23:27 +08:00
|
|
|
CFBundleURLTypes: [
|
|
|
|
|
{
|
|
|
|
|
CFBundleURLName: 'LobeHub Protocol',
|
|
|
|
|
CFBundleURLSchemes: [protocolScheme],
|
|
|
|
|
},
|
|
|
|
|
],
|
2025-12-30 16:06:44 +08:00
|
|
|
NSAppleEventsUsageDescription:
|
|
|
|
|
'Application needs to control System Settings to help you grant Full Disk Access automatically.',
|
2025-05-11 10:57:31 +08:00
|
|
|
NSCameraUsageDescription: "Application requests access to the device's camera.",
|
|
|
|
|
NSDocumentsFolderUsageDescription:
|
|
|
|
|
"Application requests access to the user's Documents folder.",
|
|
|
|
|
NSDownloadsFolderUsageDescription:
|
|
|
|
|
"Application requests access to the user's Downloads folder.",
|
|
|
|
|
NSMicrophoneUsageDescription: "Application requests access to the device's microphone.",
|
2025-12-30 16:06:44 +08:00
|
|
|
NSScreenCaptureUsageDescription:
|
|
|
|
|
'Application requests access to record and analyze screen content for AI assistance.',
|
2025-05-11 10:57:31 +08:00
|
|
|
},
|
2025-04-27 11:57:06 +08:00
|
|
|
gatekeeperAssess: false,
|
2025-09-23 10:54:36 +08:00
|
|
|
hardenedRuntime: hasAppleCertificate,
|
|
|
|
|
notarize: hasAppleCertificate,
|
|
|
|
|
...(hasAppleCertificate ? {} : { identity: null }),
|
2025-04-28 22:01:41 +08:00
|
|
|
target:
|
2025-09-08 23:46:57 +08:00
|
|
|
// 降低构建时间,nightly 只打 dmg
|
|
|
|
|
// 根据当前机器架构只构建对应架构的包
|
2025-04-28 22:01:41 +08:00
|
|
|
isNightly
|
2025-09-08 23:46:57 +08:00
|
|
|
? [{ arch: [arch === 'arm64' ? 'arm64' : 'x64'], target: 'dmg' }]
|
2025-04-28 22:01:41 +08:00
|
|
|
: [
|
2025-09-08 23:46:57 +08:00
|
|
|
{ arch: [arch === 'arm64' ? 'arm64' : 'x64'], target: 'dmg' },
|
|
|
|
|
{ arch: [arch === 'arm64' ? 'arm64' : 'x64'], target: 'zip' },
|
2025-04-28 22:01:41 +08:00
|
|
|
],
|
2025-04-27 11:57:06 +08:00
|
|
|
},
|
|
|
|
|
npmRebuild: true,
|
|
|
|
|
nsis: {
|
2025-05-01 16:42:26 +08:00
|
|
|
allowToChangeInstallationDirectory: true,
|
2025-04-27 11:57:06 +08:00
|
|
|
artifactName: '${productName}-${version}-setup.${ext}',
|
|
|
|
|
createDesktopShortcut: 'always',
|
2025-05-14 23:06:30 +08:00
|
|
|
installerHeader: './build/nsis-header.bmp',
|
|
|
|
|
installerSidebar: './build/nsis-sidebar.bmp',
|
2025-05-01 16:42:26 +08:00
|
|
|
oneClick: false,
|
2025-04-27 11:57:06 +08:00
|
|
|
shortcutName: '${productName}',
|
|
|
|
|
uninstallDisplayName: '${productName}',
|
2025-05-14 23:06:30 +08:00
|
|
|
uninstallerSidebar: './build/nsis-sidebar.bmp',
|
2025-04-27 11:57:06 +08:00
|
|
|
},
|
2025-08-06 13:23:27 +08:00
|
|
|
protocols: [
|
|
|
|
|
{
|
|
|
|
|
name: 'LobeHub Protocol',
|
|
|
|
|
schemes: [protocolScheme],
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-01-15 17:26:19 +08:00
|
|
|
publish: getPublishConfig(),
|
|
|
|
|
|
|
|
|
|
// Release notes 配置
|
|
|
|
|
// 可以通过环境变量 RELEASE_NOTES 传入,或从文件读取
|
|
|
|
|
// 这会被写入 latest-mac.yml / latest.yml 中,供 generic provider 使用
|
|
|
|
|
releaseInfo: {
|
|
|
|
|
releaseNotes: process.env.RELEASE_NOTES || undefined,
|
|
|
|
|
},
|
|
|
|
|
|
2025-04-27 11:57:06 +08:00
|
|
|
win: {
|
|
|
|
|
executableName: 'LobeHub',
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-10 23:15:42 +08:00
|
|
|
export default config;
|