9.1 应用打包概述

9.1.1 Sciter 应用打包原理

graph TD
    A[源代码] --> B[资源收集]
    B --> C[依赖分析]
    C --> D[代码压缩]
    D --> E[资源打包]
    E --> F[可执行文件]
    F --> G[安装包]
    
    H[配置文件] --> B
    I[静态资源] --> B
    J[第三方库] --> C

9.1.2 打包工具配置

// build-config.js
class BuildConfig {
    constructor() {
        this.config = {
            // 应用信息
            app: {
                name: 'MySciterApp',
                version: '1.0.0',
                description: 'A Sciter-JS Application',
                author: 'Your Name',
                icon: 'assets/icon.ico'
            },
            
            // 构建选项
            build: {
                target: 'windows', // windows, macos, linux
                architecture: 'x64', // x86, x64, arm64
                compression: true,
                minify: true,
                obfuscate: false
            },
            
            // 输出配置
            output: {
                directory: 'dist',
                filename: 'app.exe',
                installer: true
            },
            
            // 资源配置
            resources: {
                include: ['src/**/*', 'assets/**/*'],
                exclude: ['**/*.tmp', '**/*.log'],
                compress: ['**/*.js', '**/*.css', '**/*.html']
            }
        };
    }
    
    // 获取配置
    getConfig() {
        return this.config;
    }
    
    // 更新配置
    updateConfig(updates) {
        Object.assign(this.config, updates);
    }
    
    // 验证配置
    validate() {
        const required = ['app.name', 'app.version', 'build.target'];
        const missing = required.filter(key => {
            const value = this.getNestedValue(this.config, key);
            return !value;
        });
        
        if (missing.length > 0) {
            throw new Error(`Missing required config: ${missing.join(', ')}`);
        }
        
        return true;
    }
    
    // 获取嵌套值
    getNestedValue(obj, path) {
        return path.split('.').reduce((current, key) => {
            return current && current[key];
        }, obj);
    }
}

// 导出配置实例
const buildConfig = new BuildConfig();
export default buildConfig;

9.2 资源管理与优化

9.2.1 资源收集器

// resource-collector.js
import { promises as fs } from 'fs';
import path from 'path';
import glob from 'glob';

class ResourceCollector {
    constructor(config) {
        this.config = config;
        this.resources = new Map();
    }
    
    // 收集所有资源
    async collectResources() {
        console.log('开始收集资源...');
        
        // 收集包含的文件
        for (const pattern of this.config.resources.include) {
            await this.collectByPattern(pattern, true);
        }
        
        // 排除指定文件
        for (const pattern of this.config.resources.exclude) {
            await this.collectByPattern(pattern, false);
        }
        
        console.log(`收集完成,共 ${this.resources.size} 个资源`);
        return this.resources;
    }
    
    // 按模式收集
    async collectByPattern(pattern, include) {
        const files = await this.globPromise(pattern);
        
        for (const file of files) {
            if (include) {
                await this.addResource(file);
            } else {
                this.removeResource(file);
            }
        }
    }
    
    // 添加资源
    async addResource(filePath) {
        try {
            const stats = await fs.stat(filePath);
            if (stats.isFile()) {
                const content = await fs.readFile(filePath);
                this.resources.set(filePath, {
                    path: filePath,
                    size: stats.size,
                    content: content,
                    type: this.getFileType(filePath),
                    compressed: false
                });
            }
        } catch (error) {
            console.warn(`无法读取文件: ${filePath}`, error.message);
        }
    }
    
    // 移除资源
    removeResource(filePath) {
        this.resources.delete(filePath);
    }
    
    // 获取文件类型
    getFileType(filePath) {
        const ext = path.extname(filePath).toLowerCase();
        const typeMap = {
            '.js': 'javascript',
            '.css': 'stylesheet',
            '.html': 'markup',
            '.htm': 'markup',
            '.json': 'data',
            '.png': 'image',
            '.jpg': 'image',
            '.jpeg': 'image',
            '.gif': 'image',
            '.svg': 'image',
            '.ico': 'icon',
            '.ttf': 'font',
            '.woff': 'font',
            '.woff2': 'font'
        };
        
        return typeMap[ext] || 'binary';
    }
    
    // Glob Promise 包装
    globPromise(pattern) {
        return new Promise((resolve, reject) => {
            glob(pattern, (error, files) => {
                if (error) reject(error);
                else resolve(files);
            });
        });
    }
    
    // 获取资源统计
    getStats() {
        const stats = {
            total: this.resources.size,
            types: {},
            totalSize: 0
        };
        
        for (const resource of this.resources.values()) {
            stats.totalSize += resource.size;
            stats.types[resource.type] = (stats.types[resource.type] || 0) + 1;
        }
        
        return stats;
    }
}

export default ResourceCollector;

9.2.2 资源压缩器

// resource-compressor.js
import { minify as minifyJS } from 'terser';
import CleanCSS from 'clean-css';
import { minify as minifyHTML } from 'html-minifier-terser';
import zlib from 'zlib';
import { promisify } from 'util';

const gzip = promisify(zlib.gzip);
const deflate = promisify(zlib.deflate);

class ResourceCompressor {
    constructor(config) {
        this.config = config;
        this.cssMinifier = new CleanCSS({
            level: 2,
            returnPromise: true
        });
    }
    
    // 压缩资源
    async compressResources(resources) {
        console.log('开始压缩资源...');
        
        const compressPromises = [];
        
        for (const [path, resource] of resources) {
            if (this.shouldCompress(path)) {
                compressPromises.push(this.compressResource(resource));
            }
        }
        
        await Promise.all(compressPromises);
        console.log('资源压缩完成');
        
        return resources;
    }
    
    // 判断是否需要压缩
    shouldCompress(filePath) {
        return this.config.resources.compress.some(pattern => {
            return this.matchPattern(filePath, pattern);
        });
    }
    
    // 压缩单个资源
    async compressResource(resource) {
        try {
            const originalSize = resource.size;
            
            switch (resource.type) {
                case 'javascript':
                    resource.content = await this.compressJavaScript(resource.content);
                    break;
                case 'stylesheet':
                    resource.content = await this.compressCSS(resource.content);
                    break;
                case 'markup':
                    resource.content = await this.compressHTML(resource.content);
                    break;
                default:
                    // 对于其他类型,使用通用压缩
                    if (resource.size > 1024) { // 只压缩大于1KB的文件
                        resource.content = await gzip(resource.content);
                        resource.compressed = 'gzip';
                    }
                    break;
            }
            
            const newSize = Buffer.byteLength(resource.content);
            const ratio = ((originalSize - newSize) / originalSize * 100).toFixed(2);
            
            console.log(`${resource.path}: ${originalSize} -> ${newSize} bytes (${ratio}% 减少)`);
            
            resource.size = newSize;
            resource.originalSize = originalSize;
            resource.compressionRatio = ratio;
            
        } catch (error) {
            console.warn(`压缩失败: ${resource.path}`, error.message);
        }
    }
    
    // 压缩 JavaScript
    async compressJavaScript(content) {
        const result = await minifyJS(content.toString(), {
            compress: {
                drop_console: true,
                drop_debugger: true,
                pure_funcs: ['console.log', 'console.info']
            },
            mangle: {
                toplevel: true
            },
            format: {
                comments: false
            }
        });
        
        return Buffer.from(result.code);
    }
    
    // 压缩 CSS
    async compressCSS(content) {
        const result = await this.cssMinifier.minify(content.toString());
        return Buffer.from(result.styles);
    }
    
    // 压缩 HTML
    async compressHTML(content) {
        const result = await minifyHTML(content.toString(), {
            collapseWhitespace: true,
            removeComments: true,
            removeRedundantAttributes: true,
            removeScriptTypeAttributes: true,
            removeStyleLinkTypeAttributes: true,
            useShortDoctype: true,
            minifyCSS: true,
            minifyJS: true
        });
        
        return Buffer.from(result);
    }
    
    // 模式匹配
    matchPattern(str, pattern) {
        const regex = new RegExp(pattern.replace(/\*/g, '.*'));
        return regex.test(str);
    }
}

export default ResourceCompressor;

9.3 构建流程管理

9.3.1 构建管理器

// build-manager.js
import BuildConfig from './build-config.js';
import ResourceCollector from './resource-collector.js';
import ResourceCompressor from './resource-compressor.js';
import { promises as fs } from 'fs';
import path from 'path';

class BuildManager {
    constructor() {
        this.config = BuildConfig.getConfig();
        this.collector = new ResourceCollector(this.config);
        this.compressor = new ResourceCompressor(this.config);
        this.buildSteps = [];
    }
    
    // 执行完整构建
    async build() {
        try {
            console.log('开始构建应用...');
            const startTime = Date.now();
            
            // 验证配置
            BuildConfig.validate();
            
            // 执行构建步骤
            await this.executeBuildSteps();
            
            const duration = Date.now() - startTime;
            console.log(`构建完成,耗时: ${duration}ms`);
            
            return true;
            
        } catch (error) {
            console.error('构建失败:', error.message);
            throw error;
        }
    }
    
    // 执行构建步骤
    async executeBuildSteps() {
        const steps = [
            { name: '清理输出目录', fn: () => this.cleanOutput() },
            { name: '收集资源', fn: () => this.collectResources() },
            { name: '压缩资源', fn: () => this.compressResources() },
            { name: '生成清单', fn: () => this.generateManifest() },
            { name: '打包应用', fn: () => this.packageApp() },
            { name: '创建安装包', fn: () => this.createInstaller() }
        ];
        
        for (const step of steps) {
            console.log(`执行步骤: ${step.name}`);
            await step.fn();
            console.log(`✓ ${step.name} 完成`);
        }
    }
    
    // 清理输出目录
    async cleanOutput() {
        const outputDir = this.config.output.directory;
        
        try {
            await fs.rmdir(outputDir, { recursive: true });
        } catch (error) {
            // 目录不存在,忽略错误
        }
        
        await fs.mkdir(outputDir, { recursive: true });
    }
    
    // 收集资源
    async collectResources() {
        this.resources = await this.collector.collectResources();
        
        const stats = this.collector.getStats();
        console.log(`收集到 ${stats.total} 个资源,总大小: ${this.formatBytes(stats.totalSize)}`);
        
        return this.resources;
    }
    
    // 压缩资源
    async compressResources() {
        if (this.config.build.compression) {
            await this.compressor.compressResources(this.resources);
        }
        
        return this.resources;
    }
    
    // 生成应用清单
    async generateManifest() {
        const manifest = {
            name: this.config.app.name,
            version: this.config.app.version,
            description: this.config.app.description,
            author: this.config.app.author,
            buildTime: new Date().toISOString(),
            resources: Array.from(this.resources.keys()),
            totalSize: Array.from(this.resources.values())
                .reduce((sum, resource) => sum + resource.size, 0)
        };
        
        const manifestPath = path.join(this.config.output.directory, 'manifest.json');
        await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
        
        return manifest;
    }
    
    // 打包应用
    async packageApp() {
        const outputPath = path.join(
            this.config.output.directory,
            this.config.output.filename
        );
        
        // 这里应该调用 Sciter 的打包工具
        // 示例:使用 sciter-sdk 的打包功能
        await this.createSciterPackage(outputPath);
        
        return outputPath;
    }
    
    // 创建 Sciter 包
    async createSciterPackage(outputPath) {
        // 创建资源包
        const resourcesData = this.serializeResources();
        
        // 写入可执行文件
        // 这里需要根据实际的 Sciter SDK 进行调整
        const executableTemplate = await this.getExecutableTemplate();
        const packagedApp = Buffer.concat([executableTemplate, resourcesData]);
        
        await fs.writeFile(outputPath, packagedApp);
        
        console.log(`应用已打包到: ${outputPath}`);
    }
    
    // 序列化资源
    serializeResources() {
        const resourcesArray = Array.from(this.resources.entries()).map(([path, resource]) => ({
            path,
            content: resource.content.toString('base64'),
            type: resource.type,
            size: resource.size
        }));
        
        return Buffer.from(JSON.stringify(resourcesArray));
    }
    
    // 获取可执行文件模板
    async getExecutableTemplate() {
        const templatePath = `templates/${this.config.build.target}-${this.config.build.architecture}.exe`;
        return await fs.readFile(templatePath);
    }
    
    // 创建安装包
    async createInstaller() {
        if (!this.config.output.installer) {
            return;
        }
        
        const installerConfig = {
            appName: this.config.app.name,
            appVersion: this.config.app.version,
            appDescription: this.config.app.description,
            appIcon: this.config.app.icon,
            executablePath: path.join(
                this.config.output.directory,
                this.config.output.filename
            ),
            outputPath: path.join(
                this.config.output.directory,
                `${this.config.app.name}-${this.config.app.version}-setup.exe`
            )
        };
        
        await this.generateInstaller(installerConfig);
    }
    
    // 生成安装程序
    async generateInstaller(config) {
        // 这里可以使用 NSIS、Inno Setup 或其他安装包生成工具
        console.log(`创建安装包: ${config.outputPath}`);
        
        // 示例:生成 NSIS 脚本
        const nsisScript = this.generateNSISScript(config);
        const scriptPath = path.join(this.config.output.directory, 'installer.nsi');
        
        await fs.writeFile(scriptPath, nsisScript);
        
        // 调用 NSIS 编译器
        // await this.runNSISCompiler(scriptPath);
    }
    
    // 生成 NSIS 脚本
    generateNSISScript(config) {
        return `
!define APP_NAME "${config.appName}"
!define APP_VERSION "${config.appVersion}"
!define APP_DESCRIPTION "${config.appDescription}"
!define APP_EXECUTABLE "${path.basename(config.executablePath)}"

Name "\${APP_NAME}"
OutFile "${config.outputPath}"
InstallDir "$PROGRAMFILES\\\${APP_NAME}"

Section "Main"
    SetOutPath "$INSTDIR"
    File "${config.executablePath}"
    
    CreateDirectory "$SMPROGRAMS\\\${APP_NAME}"
    CreateShortCut "$SMPROGRAMS\\\${APP_NAME}\\\${APP_NAME}.lnk" "$INSTDIR\\\${APP_EXECUTABLE}"
    CreateShortCut "$DESKTOP\\\${APP_NAME}.lnk" "$INSTDIR\\\${APP_EXECUTABLE}"
    
    WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APP_NAME}" "DisplayName" "\${APP_NAME}"
    WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APP_NAME}" "UninstallString" "$INSTDIR\\uninstall.exe"
    
    WriteUninstaller "$INSTDIR\\uninstall.exe"
SectionEnd

Section "Uninstall"
    Delete "$INSTDIR\\\${APP_EXECUTABLE}"
    Delete "$INSTDIR\\uninstall.exe"
    RMDir "$INSTDIR"
    
    Delete "$SMPROGRAMS\\\${APP_NAME}\\\${APP_NAME}.lnk"
    RMDir "$SMPROGRAMS\\\${APP_NAME}"
    Delete "$DESKTOP\\\${APP_NAME}.lnk"
    
    DeleteRegKey HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APP_NAME}"
SectionEnd
`;
    }
    
    // 格式化字节数
    formatBytes(bytes) {
        const sizes = ['Bytes', 'KB', 'MB', 'GB'];
        if (bytes === 0) return '0 Bytes';
        const i = Math.floor(Math.log(bytes) / Math.log(1024));
        return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
    }
}

export default BuildManager;

9.4 部署策略

9.4.1 部署配置管理

// deployment-config.js
class DeploymentConfig {
    constructor() {
        this.environments = {
            development: {
                name: 'Development',
                url: 'http://localhost:3000',
                apiUrl: 'http://localhost:8080/api',
                debug: true,
                logLevel: 'debug'
            },
            
            staging: {
                name: 'Staging',
                url: 'https://staging.example.com',
                apiUrl: 'https://staging-api.example.com',
                debug: false,
                logLevel: 'info'
            },
            
            production: {
                name: 'Production',
                url: 'https://app.example.com',
                apiUrl: 'https://api.example.com',
                debug: false,
                logLevel: 'error'
            }
        };
        
        this.currentEnvironment = 'development';
    }
    
    // 设置环境
    setEnvironment(env) {
        if (!this.environments[env]) {
            throw new Error(`Unknown environment: ${env}`);
        }
        
        this.currentEnvironment = env;
        return this.getCurrentConfig();
    }
    
    // 获取当前配置
    getCurrentConfig() {
        return {
            environment: this.currentEnvironment,
            ...this.environments[this.currentEnvironment]
        };
    }
    
    // 生成环境配置文件
    generateConfigFile() {
        const config = this.getCurrentConfig();
        
        return `
// 自动生成的环境配置文件
// 环境: ${config.environment}
// 生成时间: ${new Date().toISOString()}

window.APP_CONFIG = ${JSON.stringify(config, null, 2)};

// 全局配置访问器
window.getConfig = function(key) {
    return key ? window.APP_CONFIG[key] : window.APP_CONFIG;
};

// 环境检查函数
window.isDevelopment = function() {
    return window.APP_CONFIG.environment === 'development';
};

window.isProduction = function() {
    return window.APP_CONFIG.environment === 'production';
};

window.isStaging = function() {
    return window.APP_CONFIG.environment === 'staging';
};
`;
    }
}

export default DeploymentConfig;

9.4.2 自动部署脚本

// auto-deploy.js
import BuildManager from './build-manager.js';
import DeploymentConfig from './deployment-config.js';
import { promises as fs } from 'fs';
import path from 'path';
import { exec } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);

class AutoDeploy {
    constructor() {
        this.buildManager = new BuildManager();
        this.deployConfig = new DeploymentConfig();
        this.deploymentSteps = [];
    }
    
    // 执行自动部署
    async deploy(environment, options = {}) {
        try {
            console.log(`开始部署到 ${environment} 环境...`);
            const startTime = Date.now();
            
            // 设置环境
            this.deployConfig.setEnvironment(environment);
            
            // 执行部署步骤
            await this.executeDeploymentSteps(options);
            
            const duration = Date.now() - startTime;
            console.log(`部署完成,耗时: ${duration}ms`);
            
            return true;
            
        } catch (error) {
            console.error('部署失败:', error.message);
            throw error;
        }
    }
    
    // 执行部署步骤
    async executeDeploymentSteps(options) {
        const steps = [
            { name: '生成环境配置', fn: () => this.generateEnvironmentConfig() },
            { name: '构建应用', fn: () => this.buildApplication() },
            { name: '运行测试', fn: () => this.runTests(), skip: options.skipTests },
            { name: '上传文件', fn: () => this.uploadFiles(), skip: options.skipUpload },
            { name: '更新服务', fn: () => this.updateService(), skip: options.skipService },
            { name: '验证部署', fn: () => this.verifyDeployment() }
        ];
        
        for (const step of steps) {
            if (step.skip) {
                console.log(`跳过步骤: ${step.name}`);
                continue;
            }
            
            console.log(`执行步骤: ${step.name}`);
            await step.fn();
            console.log(`✓ ${step.name} 完成`);
        }
    }
    
    // 生成环境配置
    async generateEnvironmentConfig() {
        const configContent = this.deployConfig.generateConfigFile();
        const configPath = path.join('src', 'config', 'environment.js');
        
        await fs.mkdir(path.dirname(configPath), { recursive: true });
        await fs.writeFile(configPath, configContent);
        
        console.log(`环境配置已生成: ${configPath}`);
    }
    
    // 构建应用
    async buildApplication() {
        await this.buildManager.build();
    }
    
    // 运行测试
    async runTests() {
        console.log('运行单元测试...');
        
        try {
            const { stdout, stderr } = await execAsync('npm test');
            console.log('测试输出:', stdout);
            
            if (stderr) {
                console.warn('测试警告:', stderr);
            }
            
        } catch (error) {
            console.error('测试失败:', error.message);
            throw new Error('测试未通过,部署中止');
        }
    }
    
    // 上传文件
    async uploadFiles() {
        const config = this.deployConfig.getCurrentConfig();
        
        if (config.environment === 'development') {
            console.log('开发环境,跳过文件上传');
            return;
        }
        
        // 这里可以集成各种上传方式
        await this.uploadToServer();
    }
    
    // 上传到服务器
    async uploadToServer() {
        const config = this.deployConfig.getCurrentConfig();
        
        // 示例:使用 SCP 上传
        const localPath = 'dist/*';
        const remotePath = `/var/www/${config.name.toLowerCase()}/`;
        const serverHost = this.extractHostFromUrl(config.url);
        
        console.log(`上传文件到服务器: ${serverHost}`);
        
        try {
            const command = `scp -r ${localPath} user@${serverHost}:${remotePath}`;
            const { stdout, stderr } = await execAsync(command);
            
            if (stderr) {
                console.warn('上传警告:', stderr);
            }
            
            console.log('文件上传完成');
            
        } catch (error) {
            console.error('文件上传失败:', error.message);
            throw error;
        }
    }
    
    // 更新服务
    async updateService() {
        const config = this.deployConfig.getCurrentConfig();
        
        if (config.environment === 'development') {
            console.log('开发环境,跳过服务更新');
            return;
        }
        
        console.log('更新远程服务...');
        
        // 示例:重启服务
        const serverHost = this.extractHostFromUrl(config.url);
        const restartCommand = `ssh user@${serverHost} 'sudo systemctl restart myapp'`;
        
        try {
            await execAsync(restartCommand);
            console.log('服务重启完成');
            
        } catch (error) {
            console.error('服务更新失败:', error.message);
            throw error;
        }
    }
    
    // 验证部署
    async verifyDeployment() {
        const config = this.deployConfig.getCurrentConfig();
        
        console.log('验证部署状态...');
        
        // 检查应用是否可访问
        const isAccessible = await this.checkUrlAccessibility(config.url);
        
        if (!isAccessible) {
            throw new Error(`应用无法访问: ${config.url}`);
        }
        
        // 检查 API 是否正常
        const isApiHealthy = await this.checkApiHealth(config.apiUrl);
        
        if (!isApiHealthy) {
            console.warn(`API 健康检查失败: ${config.apiUrl}`);
        }
        
        console.log('部署验证通过');
    }
    
    // 检查 URL 可访问性
    async checkUrlAccessibility(url) {
        try {
            const response = await fetch(url, { method: 'HEAD' });
            return response.ok;
        } catch (error) {
            console.error(`URL 检查失败: ${url}`, error.message);
            return false;
        }
    }
    
    // 检查 API 健康状态
    async checkApiHealth(apiUrl) {
        try {
            const healthUrl = `${apiUrl}/health`;
            const response = await fetch(healthUrl);
            
            if (response.ok) {
                const data = await response.json();
                return data.status === 'healthy';
            }
            
            return false;
            
        } catch (error) {
            console.error(`API 健康检查失败: ${apiUrl}`, error.message);
            return false;
        }
    }
    
    // 从 URL 提取主机名
    extractHostFromUrl(url) {
        try {
            const urlObj = new URL(url);
            return urlObj.hostname;
        } catch (error) {
            throw new Error(`无效的 URL: ${url}`);
        }
    }
}

export default AutoDeploy;

9.5 版本管理与发布

9.5.1 版本管理器

// version-manager.js
import { promises as fs } from 'fs';
import path from 'path';
import { exec } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);

class VersionManager {
    constructor() {
        this.packageJsonPath = 'package.json';
        this.changelogPath = 'CHANGELOG.md';
    }
    
    // 获取当前版本
    async getCurrentVersion() {
        try {
            const packageJson = await this.readPackageJson();
            return packageJson.version;
        } catch (error) {
            throw new Error('无法读取当前版本');
        }
    }
    
    // 更新版本
    async updateVersion(type = 'patch') {
        const currentVersion = await this.getCurrentVersion();
        const newVersion = this.incrementVersion(currentVersion, type);
        
        console.log(`版本更新: ${currentVersion} -> ${newVersion}`);
        
        // 更新 package.json
        await this.updatePackageVersion(newVersion);
        
        // 更新 changelog
        await this.updateChangelog(newVersion);
        
        // 创建 Git 标签
        await this.createGitTag(newVersion);
        
        return newVersion;
    }
    
    // 版本号递增
    incrementVersion(version, type) {
        const parts = version.split('.').map(Number);
        
        switch (type) {
            case 'major':
                parts[0]++;
                parts[1] = 0;
                parts[2] = 0;
                break;
            case 'minor':
                parts[1]++;
                parts[2] = 0;
                break;
            case 'patch':
            default:
                parts[2]++;
                break;
        }
        
        return parts.join('.');
    }
    
    // 读取 package.json
    async readPackageJson() {
        const content = await fs.readFile(this.packageJsonPath, 'utf8');
        return JSON.parse(content);
    }
    
    // 更新 package.json 版本
    async updatePackageVersion(version) {
        const packageJson = await this.readPackageJson();
        packageJson.version = version;
        
        const content = JSON.stringify(packageJson, null, 2);
        await fs.writeFile(this.packageJsonPath, content);
        
        console.log(`package.json 版本已更新为: ${version}`);
    }
    
    // 更新 changelog
    async updateChangelog(version) {
        const date = new Date().toISOString().split('T')[0];
        const newEntry = `\n## [${version}] - ${date}\n\n### Added\n- 新功能描述\n\n### Changed\n- 变更描述\n\n### Fixed\n- 修复描述\n`;
        
        try {
            let changelog = await fs.readFile(this.changelogPath, 'utf8');
            
            // 在第一个版本条目前插入新条目
            const insertIndex = changelog.indexOf('## [');
            if (insertIndex !== -1) {
                changelog = changelog.slice(0, insertIndex) + newEntry + changelog.slice(insertIndex);
            } else {
                changelog += newEntry;
            }
            
            await fs.writeFile(this.changelogPath, changelog);
            
        } catch (error) {
            // 如果 changelog 不存在,创建新的
            const newChangelog = `# Changelog\n\nAll notable changes to this project will be documented in this file.${newEntry}`;
            await fs.writeFile(this.changelogPath, newChangelog);
        }
        
        console.log(`CHANGELOG.md 已更新`);
    }
    
    // 创建 Git 标签
    async createGitTag(version) {
        try {
            // 添加文件到 Git
            await execAsync('git add package.json CHANGELOG.md');
            
            // 提交更改
            await execAsync(`git commit -m "chore: bump version to ${version}"`);
            
            // 创建标签
            await execAsync(`git tag -a v${version} -m "Release version ${version}"`);
            
            console.log(`Git 标签 v${version} 已创建`);
            
        } catch (error) {
            console.warn('Git 操作失败:', error.message);
        }
    }
    
    // 获取版本历史
    async getVersionHistory() {
        try {
            const { stdout } = await execAsync('git tag --sort=-version:refname');
            const tags = stdout.trim().split('\n').filter(tag => tag.startsWith('v'));
            
            const history = [];
            
            for (const tag of tags) {
                const { stdout: commitInfo } = await execAsync(`git log -1 --format="%ci|%s" ${tag}`);
                const [date, message] = commitInfo.trim().split('|');
                
                history.push({
                    version: tag.substring(1), // 移除 'v' 前缀
                    tag: tag,
                    date: new Date(date),
                    message: message
                });
            }
            
            return history;
            
        } catch (error) {
            console.warn('无法获取版本历史:', error.message);
            return [];
        }
    }
    
    // 生成发布说明
    async generateReleaseNotes(version) {
        try {
            const previousVersion = await this.getPreviousVersion(version);
            const { stdout } = await execAsync(`git log v${previousVersion}..v${version} --pretty=format:"- %s (%h)"`);
            
            const commits = stdout.trim().split('\n').filter(line => line.length > 0);
            
            const releaseNotes = `
# Release Notes - v${version}

## Changes

${commits.join('\n')}

## Installation

\`\`\`bash
npm install myapp@${version}
\`\`\`

## Download

- [Windows Installer](releases/v${version}/myapp-${version}-setup.exe)
- [Portable Version](releases/v${version}/myapp-${version}-portable.zip)
`;
            
            return releaseNotes;
            
        } catch (error) {
            console.warn('无法生成发布说明:', error.message);
            return `# Release Notes - v${version}\n\n发布说明生成失败。`;
        }
    }
    
    // 获取上一个版本
    async getPreviousVersion(currentVersion) {
        const history = await this.getVersionHistory();
        const currentIndex = history.findIndex(item => item.version === currentVersion);
        
        if (currentIndex > 0) {
            return history[currentIndex + 1].version;
        }
        
        return '0.0.0';
    }
}

export default VersionManager;

9.5.2 发布管理器

// release-manager.js
import VersionManager from './version-manager.js';
import BuildManager from './build-manager.js';
import AutoDeploy from './auto-deploy.js';
import { promises as fs } from 'fs';
import path from 'path';
import { exec } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);

class ReleaseManager {
    constructor() {
        this.versionManager = new VersionManager();
        this.buildManager = new BuildManager();
        this.autoDeploy = new AutoDeploy();
        this.releaseDir = 'releases';
    }
    
    // 执行完整发布流程
    async release(options = {}) {
        try {
            console.log('开始发布流程...');
            const startTime = Date.now();
            
            // 执行发布步骤
            const releaseInfo = await this.executeReleaseSteps(options);
            
            const duration = Date.now() - startTime;
            console.log(`发布完成,耗时: ${duration}ms`);
            
            return releaseInfo;
            
        } catch (error) {
            console.error('发布失败:', error.message);
            throw error;
        }
    }
    
    // 执行发布步骤
    async executeReleaseSteps(options) {
        const steps = [
            { name: '版本检查', fn: () => this.checkVersion(options) },
            { name: '更新版本', fn: () => this.updateVersion(options), skip: options.skipVersionUpdate },
            { name: '构建应用', fn: () => this.buildApplication() },
            { name: '运行测试', fn: () => this.runTests(), skip: options.skipTests },
            { name: '创建发布包', fn: () => this.createReleasePackage() },
            { name: '生成发布说明', fn: () => this.generateReleaseNotes() },
            { name: '发布到仓库', fn: () => this.publishToRepository(), skip: options.skipPublish },
            { name: '部署到生产', fn: () => this.deployToProduction(), skip: options.skipDeploy }
        ];
        
        let releaseInfo = {};
        
        for (const step of steps) {
            if (step.skip) {
                console.log(`跳过步骤: ${step.name}`);
                continue;
            }
            
            console.log(`执行步骤: ${step.name}`);
            const result = await step.fn();
            
            if (result) {
                Object.assign(releaseInfo, result);
            }
            
            console.log(`✓ ${step.name} 完成`);
        }
        
        return releaseInfo;
    }
    
    // 检查版本
    async checkVersion(options) {
        const currentVersion = await this.versionManager.getCurrentVersion();
        console.log(`当前版本: ${currentVersion}`);
        
        if (options.version) {
            console.log(`目标版本: ${options.version}`);
        }
        
        return { currentVersion };
    }
    
    // 更新版本
    async updateVersion(options) {
        const versionType = options.versionType || 'patch';
        const newVersion = await this.versionManager.updateVersion(versionType);
        
        return { newVersion };
    }
    
    // 构建应用
    async buildApplication() {
        await this.buildManager.build();
        return { buildCompleted: true };
    }
    
    // 运行测试
    async runTests() {
        console.log('运行完整测试套件...');
        
        try {
            // 单元测试
            await execAsync('npm run test:unit');
            console.log('✓ 单元测试通过');
            
            // 集成测试
            await execAsync('npm run test:integration');
            console.log('✓ 集成测试通过');
            
            // E2E 测试
            await execAsync('npm run test:e2e');
            console.log('✓ E2E 测试通过');
            
            return { testsCompleted: true };
            
        } catch (error) {
            console.error('测试失败:', error.message);
            throw new Error('测试未通过,发布中止');
        }
    }
    
    // 创建发布包
    async createReleasePackage() {
        const version = await this.versionManager.getCurrentVersion();
        const releaseVersionDir = path.join(this.releaseDir, `v${version}`);
        
        // 创建发布目录
        await fs.mkdir(releaseVersionDir, { recursive: true });
        
        // 复制构建产物
        await this.copyBuildArtifacts(releaseVersionDir);
        
        // 创建压缩包
        const packagePath = await this.createZipPackage(releaseVersionDir, version);
        
        console.log(`发布包已创建: ${packagePath}`);
        
        return { 
            releasePackage: packagePath,
            releaseDir: releaseVersionDir
        };
    }
    
    // 复制构建产物
    async copyBuildArtifacts(targetDir) {
        const artifacts = [
            { src: 'dist', dest: path.join(targetDir, 'dist') },
            { src: 'package.json', dest: path.join(targetDir, 'package.json') },
            { src: 'README.md', dest: path.join(targetDir, 'README.md') },
            { src: 'CHANGELOG.md', dest: path.join(targetDir, 'CHANGELOG.md') }
        ];
        
        for (const artifact of artifacts) {
            try {
                await execAsync(`cp -r ${artifact.src} ${artifact.dest}`);
            } catch (error) {
                console.warn(`无法复制 ${artifact.src}:`, error.message);
            }
        }
    }
    
    // 创建 ZIP 包
    async createZipPackage(sourceDir, version) {
        const zipPath = path.join(this.releaseDir, `myapp-${version}.zip`);
        
        try {
            await execAsync(`cd ${sourceDir} && zip -r ../${path.basename(zipPath)} .`);
            return zipPath;
        } catch (error) {
            console.error('创建 ZIP 包失败:', error.message);
            throw error;
        }
    }
    
    // 生成发布说明
    async generateReleaseNotes() {
        const version = await this.versionManager.getCurrentVersion();
        const releaseNotes = await this.versionManager.generateReleaseNotes(version);
        
        const notesPath = path.join(this.releaseDir, `v${version}`, 'RELEASE_NOTES.md');
        await fs.writeFile(notesPath, releaseNotes);
        
        console.log(`发布说明已生成: ${notesPath}`);
        
        return { releaseNotes: notesPath };
    }
    
    // 发布到仓库
    async publishToRepository() {
        console.log('发布到 Git 仓库...');
        
        try {
            // 推送代码和标签
            await execAsync('git push origin main');
            await execAsync('git push origin --tags');
            
            console.log('代码和标签已推送到远程仓库');
            
            return { repositoryPublished: true };
            
        } catch (error) {
            console.error('仓库发布失败:', error.message);
            throw error;
        }
    }
    
    // 部署到生产环境
    async deployToProduction() {
        console.log('部署到生产环境...');
        
        await this.autoDeploy.deploy('production', {
            skipTests: true // 测试已经在发布流程中运行过
        });
        
        return { productionDeployed: true };
    }
    
    // 回滚发布
    async rollback(version) {
        console.log(`回滚到版本: ${version}`);
        
        try {
            // 检查版本是否存在
            const history = await this.versionManager.getVersionHistory();
            const targetVersion = history.find(item => item.version === version);
            
            if (!targetVersion) {
                throw new Error(`版本 ${version} 不存在`);
            }
            
            // 回滚代码
            await execAsync(`git checkout v${version}`);
            
            // 重新部署
            await this.autoDeploy.deploy('production', {
                skipTests: true,
                skipUpload: false
            });
            
            console.log(`回滚到版本 ${version} 完成`);
            
            return { rolledBack: true, version };
            
        } catch (error) {
            console.error('回滚失败:', error.message);
            throw error;
        }
    }
}

export default ReleaseManager;

9.6 实践练习

9.6.1 创建完整的构建和部署流程

// build-deploy-example.js
import BuildManager from './build-manager.js';
import AutoDeploy from './auto-deploy.js';
import ReleaseManager from './release-manager.js';

// 示例:完整的构建和部署流程
async function fullBuildAndDeploy() {
    try {
        console.log('=== 开始完整构建和部署流程 ===');
        
        // 1. 创建发布管理器
        const releaseManager = new ReleaseManager();
        
        // 2. 执行发布流程
        const releaseInfo = await releaseManager.release({
            versionType: 'minor', // 版本类型:major, minor, patch
            skipTests: false,     // 是否跳过测试
            skipPublish: false,   // 是否跳过发布到仓库
            skipDeploy: false     // 是否跳过部署
        });
        
        console.log('发布信息:', releaseInfo);
        console.log('=== 构建和部署流程完成 ===');
        
    } catch (error) {
        console.error('构建和部署失败:', error.message);
        process.exit(1);
    }
}

// 示例:仅构建应用
async function buildOnly() {
    try {
        console.log('=== 开始构建应用 ===');
        
        const buildManager = new BuildManager();
        await buildManager.build();
        
        console.log('=== 构建完成 ===');
        
    } catch (error) {
        console.error('构建失败:', error.message);
        process.exit(1);
    }
}

// 示例:仅部署到指定环境
async function deployOnly(environment) {
    try {
        console.log(`=== 开始部署到 ${environment} 环境 ===`);
        
        const autoDeploy = new AutoDeploy();
        await autoDeploy.deploy(environment);
        
        console.log('=== 部署完成 ===');
        
    } catch (error) {
        console.error('部署失败:', error.message);
        process.exit(1);
    }
}

// 根据命令行参数执行不同操作
const args = process.argv.slice(2);
const command = args[0];

switch (command) {
    case 'build':
        buildOnly();
        break;
    case 'deploy':
        const environment = args[1] || 'development';
        deployOnly(environment);
        break;
    case 'release':
        fullBuildAndDeploy();
        break;
    default:
        console.log('使用方法:');
        console.log('  node build-deploy-example.js build          # 仅构建');
        console.log('  node build-deploy-example.js deploy [env]   # 仅部署');
        console.log('  node build-deploy-example.js release       # 完整发布流程');
        break;
}

9.6.2 package.json 脚本配置

{
  "name": "sciter-js-app",
  "version": "1.0.0",
  "description": "A Sciter-JS Application",
  "scripts": {
    "build": "node scripts/build-deploy-example.js build",
    "deploy:dev": "node scripts/build-deploy-example.js deploy development",
    "deploy:staging": "node scripts/build-deploy-example.js deploy staging",
    "deploy:prod": "node scripts/build-deploy-example.js deploy production",
    "release": "node scripts/build-deploy-example.js release",
    "release:patch": "npm version patch && npm run release",
    "release:minor": "npm version minor && npm run release",
    "release:major": "npm version major && npm run release",
    "test": "jest",
    "test:unit": "jest --testPathPattern=unit",
    "test:integration": "jest --testPathPattern=integration",
    "test:e2e": "playwright test",
    "lint": "eslint src/",
    "format": "prettier --write src/"
  },
  "devDependencies": {
    "terser": "^5.0.0",
    "clean-css": "^5.0.0",
    "html-minifier-terser": "^7.0.0",
    "glob": "^8.0.0",
    "jest": "^29.0.0",
    "playwright": "^1.0.0",
    "eslint": "^8.0.0",
    "prettier": "^2.0.0"
  }
}

9.7 本章小结

9.7.1 核心概念

  1. 应用打包:将源代码、资源和依赖打包成可执行文件
  2. 资源优化:压缩和优化应用资源以减小体积
  3. 部署自动化:自动化部署流程提高效率和可靠性
  4. 版本管理:系统化管理应用版本和发布历史
  5. 发布流程:标准化的发布流程确保质量

9.7.2 技术要点

  • 构建配置管理和验证
  • 资源收集、压缩和打包
  • 多环境部署策略
  • 自动化测试和验证
  • 版本控制和标签管理
  • 发布包创建和分发

9.7.3 最佳实践

  1. 配置管理:使用配置文件管理不同环境的设置
  2. 自动化测试:在部署前运行完整的测试套件
  3. 渐进式部署:先部署到测试环境再到生产环境
  4. 版本标记:为每个发布版本创建 Git 标签
  5. 回滚准备:保持回滚到上一版本的能力
  6. 监控验证:部署后验证应用状态和功能

9.7.4 下一章预告

下一章我们将学习「实战项目案例」,通过完整的项目实例来综合运用前面学到的所有知识和技能。