mirror of
https://github.com/lobehub/lobehub.git
synced 2026-04-02 14:29:23 +07:00
* chore: stable updater * ✨ feat: add local update testing scripts and configuration - Introduced scripts for local update testing, including setup, server management, and manifest generation. - Added `dev-app-update.local.yml` for local server configuration. - Implemented `generate-manifest.sh` to create update manifests. - Created `run-test.sh` for streamlined testing process. - Updated `README.md` with instructions for local testing setup and usage. - Enhanced `UpdaterManager` to allow forced use of dev update configuration in packaged apps. Signed-off-by: Innei <tukon479@gmail.com> * 🐛 fix(desktop): update UpdaterManager test mocks for new exports Add missing mock exports for @/modules/updater/configs: - isStableChannel - githubConfig - UPDATE_SERVER_URL Add mock for @/env with getDesktopEnv Add setFeedURL method to autoUpdater mock * ✨ feat: add Conductor setup scripts and configuration * ✨ feat: enhance update modal functionality and refactor modal hooks - Added `useUpdateModal` for managing update modal state and behavior. - Refactored `UpdateModal` to utilize new modal management approach. - Improved `useWatchBroadcast` integration for handling update events. - Removed deprecated `createModalHooks` and related components from `FunctionModal`. - Updated `AddFilesToKnowledgeBase` and `CreateNew` modals to use new modal context for closing behavior. This refactor streamlines modal management and enhances the user experience during update processes. Signed-off-by: Innei <tukon479@gmail.com> * update flow (#11513) * ci: simplify desktop release workflow and add renderer tarball * 👷 ci: fix s3 upload credentials for desktop release * 🐛 fix(ci): use compact jq output for GitHub Actions matrix Add -c flag to jq commands to produce single-line JSON output, fixing "Invalid format" error when setting GITHUB_OUTPUT. * 🐛 fix(ci): add administration permission to detect self-hosted runner The /actions/runners API requires administration:read permission to list repository runners. * 🔧 refactor(ci): use workflow input for self-hosted runner selection Replace API-based runner detection with workflow input parameter since GITHUB_TOKEN lacks permission to call /actions/runners API. - Add `use_self_hosted_mac` input (default: true) - Release events always use self-hosted runner - Manual dispatch can toggle via input * feat(updater): add stable channel support with fallback mechanism - Configure electron-builder to generate stable-mac.yml for stable channel - Update CI workflow to handle both stable and latest manifest files - Implement fallback to GitHub provider when primary S3 provider fails - Reset to primary provider after successful update check * 🐛 fix(updater): remove invalid channel config from electron-builder - Remove unsupported 'channel' property from electron-builder config - Create stable*.yml files from latest*.yml in workflow instead - This ensures electron-updater finds correct manifest for stable channel * 🐛 fix(updater): use correct channel based on provider type - S3 provider: channel='stable' → looks for stable-mac.yml - GitHub provider: channel='latest' → looks for latest-mac.yml This fixes the 404 error when falling back to GitHub releases, which only have latest-mac.yml files. * refactor(env): remove unused OFFICIAL_CLOUD_SERVER and update env defaults Update environment variable handling by removing unused OFFICIAL_CLOUD_SERVER and setting defaults for UPDATE_CHANNEL and UPDATE_SERVER_URL from process.env during build stage. * 🐛 fix(ci): add version prefix to stable manifest URLs for S3 S3 directory structure: stable/{version}/xxx.dmg So stable-mac.yml URLs need version prefix: url: LobeHub-2.1.0-arm64.dmg → url: 2.1.1/LobeHub-2.1.0-arm64.dmg * ✨ feat(ci): add renderer tar manifest for integrity verification Creates stable-renderer.yml with SHA512 checksum for lobehub-renderer.tar.gz This allows the desktop app to verify renderer tarball integrity before extraction. * 🐛 fix(ci): fix YAML syntax error in renderer manifest generation * ✨ feat(ci): archive manifest files in version directory * refactor(ci): update desktop release workflows to streamline build process - Removed unnecessary dependencies in the build job for the desktop beta workflow. - Introduced a new gate job to conditionally proceed with publishing based on the success of previous jobs. - Updated macOS file merging to depend on the new gate job instead of the build job. - Simplified macOS runner selection logic in the stable workflow by using GitHub-hosted runners exclusively. Signed-off-by: Innei <tukon479@gmail.com> * refactor(electron): reorganize titlebar components and update imports - Moved titlebar components to a new directory structure for better organization. - Updated import paths for `SimpleTitleBar`, `TitleBar`, and related constants. - Introduced new components for connection management and navigation within the titlebar. - Added constants for title bar height to maintain consistency across components. This refactor enhances the maintainability of the titlebar code and improves the overall structure of the Electron application. Signed-off-by: Innei <tukon479@gmail.com> * feat(ci): add release notes handling to desktop stable workflow - Enhanced the desktop stable release workflow to include release notes. - Updated output variables to capture release notes from the GitHub event. - Adjusted environment variables in subsequent jobs to utilize the new release notes data. This addition improves the clarity and documentation of releases by ensuring that release notes are included in the workflow process. Signed-off-by: Innei <tukon479@gmail.com> * 🐛 fix: call onClose after knowledge base modal closes * 🧪 test: fix UpdaterManager update channel mocks --------- Signed-off-by: Innei <tukon479@gmail.com>
194 lines
6.4 KiB
JavaScript
194 lines
6.4 KiB
JavaScript
/* eslint-disable unicorn/no-process-exit, unicorn/prefer-top-level-await */
|
||
import fs from 'node:fs';
|
||
import path from 'node:path';
|
||
import YAML from 'yaml';
|
||
|
||
// 配置
|
||
// Support both stable-mac.yml (stable channel) and latest-mac.yml (fallback)
|
||
const STABLE_outputFileName = 'stable-mac.yml';
|
||
const LATEST_outputFileName = 'latest-mac.yml';
|
||
const RELEASE_DIR = path.resolve('release');
|
||
|
||
/**
|
||
* 检测 latest-mac.yml 文件的平台类型
|
||
* @param {Object} yamlContent - YAML 文件内容
|
||
* @returns {'x64' | 'arm64' | 'both' | 'none'}
|
||
*/
|
||
function detectPlatform(yamlContent) {
|
||
const hasX64 = yamlContent.files.some((file) => file.url.includes('-x64.dmg'));
|
||
const hasArm64 = yamlContent.files.some((file) => file.url.includes('-arm64.dmg'));
|
||
|
||
if (hasX64 && hasArm64) return 'both';
|
||
if (hasX64 && !hasArm64) return 'x64';
|
||
if (!hasX64 && hasArm64) return 'arm64';
|
||
return 'none';
|
||
}
|
||
|
||
/**
|
||
* 合并两个 latest-mac.yml 文件
|
||
* @param {Object} x64Content - x64 平台的 YAML 内容
|
||
* @param {Object} arm64Content - ARM64 平台的 YAML 内容
|
||
* @returns {string} 合并后的 YAML 字符串
|
||
*/
|
||
function mergeYamlFiles(x64Content, arm64Content) {
|
||
// 以 ARM64 为基础(Apple Silicon 优先)
|
||
const merged = {
|
||
...arm64Content,
|
||
files: [...arm64Content.files, ...x64Content.files],
|
||
};
|
||
|
||
return YAML.stringify(merged);
|
||
}
|
||
|
||
/**
|
||
* 读取本地文件
|
||
* @param {string} filePath - 文件路径
|
||
* @returns {string | null} 文件内容或 null
|
||
*/
|
||
function readLocalFile(filePath) {
|
||
try {
|
||
if (fs.existsSync(filePath)) {
|
||
const content = fs.readFileSync(filePath, 'utf8');
|
||
console.log(`✅ Read local file: ${filePath} (${content.length} chars)`);
|
||
return content;
|
||
}
|
||
console.log(`⚠️ Local file not found: ${filePath}`);
|
||
return null;
|
||
} catch (error) {
|
||
console.error(`❌ Error reading local file ${filePath}:`, error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 写入本地文件
|
||
* @param {string} filePath - 文件路径
|
||
* @param {string} content - 文件内容
|
||
*/
|
||
function writeLocalFile(filePath, content) {
|
||
try {
|
||
fs.writeFileSync(filePath, content, 'utf8');
|
||
console.log(`✅ Written local file: ${filePath} (${content.length} chars)`);
|
||
} catch (error) {
|
||
console.error(`❌ Error writing local file ${filePath}:`, error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 主函数
|
||
*/
|
||
async function main() {
|
||
try {
|
||
console.log('🚀 Starting macOS Release file merge');
|
||
console.log(`📁 Working directory: ${RELEASE_DIR}`);
|
||
|
||
// 1. 检查 release 目录下的所有文件
|
||
const releaseFiles = fs.readdirSync(RELEASE_DIR);
|
||
console.log(`📂 Files in release directory: ${releaseFiles.join(', ')}`);
|
||
|
||
// 2. 查找所有 stable-mac*.yml 和 latest-mac*.yml 文件
|
||
// Prioritize stable-mac*.yml, fallback to latest-mac*.yml
|
||
const stableMacYmlFiles = releaseFiles.filter(
|
||
(f) => f.startsWith('stable-mac') && f.endsWith('.yml'),
|
||
);
|
||
const latestMacYmlFiles = releaseFiles.filter(
|
||
(f) => f.startsWith('latest-mac') && f.endsWith('.yml'),
|
||
);
|
||
|
||
// Use stable files if available, otherwise use latest
|
||
const macYmlFiles = stableMacYmlFiles.length > 0 ? stableMacYmlFiles : latestMacYmlFiles;
|
||
const outputFileName =
|
||
stableMacYmlFiles.length > 0 ? STABLE_outputFileName : LATEST_outputFileName;
|
||
|
||
console.log(`🔍 Found stable macOS YAML files: ${stableMacYmlFiles.join(', ') || 'none'}`);
|
||
console.log(`🔍 Found latest macOS YAML files: ${latestMacYmlFiles.join(', ') || 'none'}`);
|
||
console.log(`🔍 Using files: ${macYmlFiles.join(', ')} -> ${outputFileName}`);
|
||
|
||
if (macYmlFiles.length === 0) {
|
||
console.log('⚠️ No macOS YAML files found, skipping merge');
|
||
return;
|
||
}
|
||
|
||
// 3. 处理找到的文件,识别平台
|
||
const macFiles = [];
|
||
|
||
for (const fileName of macYmlFiles) {
|
||
const filePath = path.join(RELEASE_DIR, fileName);
|
||
const content = readLocalFile(filePath);
|
||
|
||
if (!content) continue;
|
||
|
||
try {
|
||
const yamlContent = YAML.parse(content);
|
||
const platform = detectPlatform(yamlContent);
|
||
|
||
if (platform === 'x64' || platform === 'arm64') {
|
||
macFiles.push({ content, filename: fileName, platform, yaml: yamlContent });
|
||
console.log(`🔍 Detected ${platform} platform in ${fileName}`);
|
||
} else if (platform === 'both') {
|
||
console.log(`✅ Found already merged file: ${fileName}`);
|
||
// 如果已经是合并后的文件,直接复制为最终文件
|
||
writeLocalFile(path.join(RELEASE_DIR, outputFileName), content);
|
||
return;
|
||
} else {
|
||
console.log(`⚠️ Unknown platform type: ${platform} in ${fileName}`);
|
||
}
|
||
} catch (error) {
|
||
console.warn(`⚠️ Failed to parse ${fileName}:`, error);
|
||
}
|
||
}
|
||
|
||
// 4. 检查是否有两个不同平台的文件
|
||
const x64Files = macFiles.filter((f) => f.platform === 'x64');
|
||
const arm64Files = macFiles.filter((f) => f.platform === 'arm64');
|
||
|
||
if (x64Files.length === 0 && arm64Files.length === 0) {
|
||
console.log('⚠️ No valid platform files found');
|
||
return;
|
||
}
|
||
|
||
if (x64Files.length === 0) {
|
||
console.log('⚠️ No x64 files found, using ARM64 only');
|
||
writeLocalFile(path.join(RELEASE_DIR, outputFileName), arm64Files[0].content);
|
||
return;
|
||
}
|
||
|
||
if (arm64Files.length === 0) {
|
||
console.log('⚠️ No ARM64 files found, using x64 only');
|
||
writeLocalFile(path.join(RELEASE_DIR, outputFileName), x64Files[0].content);
|
||
return;
|
||
}
|
||
|
||
// 5. 合并 x64 和 ARM64 文件
|
||
const x64File = x64Files[0];
|
||
const arm64File = arm64Files[0];
|
||
|
||
console.log(`🔄 Merging ${x64File.filename} (x64) and ${arm64File.filename} (ARM64)...`);
|
||
const mergedContent = mergeYamlFiles(x64File.yaml, arm64File.yaml);
|
||
|
||
// 6. 保存合并后的文件
|
||
const mergedFilePath = path.join(RELEASE_DIR, outputFileName);
|
||
writeLocalFile(mergedFilePath, mergedContent);
|
||
|
||
// 7. 验证合并结果
|
||
const mergedYaml = YAML.parse(mergedContent);
|
||
const finalPlatform = detectPlatform(mergedYaml);
|
||
|
||
if (finalPlatform === 'both') {
|
||
console.log('✅ Successfully merged both x64 and ARM64 platforms');
|
||
console.log(`📊 Final file contains ${mergedYaml.files.length} files`);
|
||
} else {
|
||
console.warn(`⚠️ Merge result unexpected: ${finalPlatform}`);
|
||
}
|
||
|
||
console.log('🎉 Merge complete!');
|
||
} catch (error) {
|
||
console.error('❌ Error during merge:', error);
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
// 运行主函数
|
||
await main();
|