UNPKG

@tianyio/quality-helper

Version:

A comprehensive quality helper tool for project scaffolding and management

1,044 lines (910 loc) 29.4 kB
#!/usr/bin/env node import { readdirSync, readFileSync, statSync, writeFileSync } from 'fs' import { execSync } from 'child_process' import { join, resolve, extname } from 'path' import prompts from 'prompts' import chalk from 'chalk' // ==================== 配置和类型定义 ==================== /** * 版本类型枚举 */ const VersionType = { PATCH: 'patch', MINOR: 'minor', MAJOR: 'major', PRERELEASE: 'prerelease', PREPATCH: 'prepatch', PREMINOR: 'preminor', PREMAJOR: 'premajor', } /** * 默认配置 */ class Config { static getDefaultConfig() { return { publicRegistry: 'https://registry.npmjs.org', privateRegistry: 'http://192.168.4.34:4873', packagesDir: resolve(process.cwd(), 'packages'), privateScope: '@privateScope', publicScope: '@publicScope', } } } // ==================== 错误处理类 ==================== /** * 自定义发布错误类 */ class PublishError extends Error { constructor(message, code = 'PUBLISH_ERROR', cause = null) { super(message) this.name = 'PublishError' this.code = code this.cause = cause } } /** * 统一错误处理器 */ class ErrorHandler { /** * 处理错误并尝试恢复状态 * @param {Error} error * @param {NpmRegistryManager} registryManager */ static handleError(error, registryManager = null) { console.error(chalk.red('❌ 发布过程中出现错误:')) console.error(chalk.red(error.message)) // 尝试恢复npm注册表地址 if (registryManager) { try { registryManager.restoreOriginalRegistry() console.log(chalk.yellow('🔄 已恢复npm注册表地址')) } catch (restoreError) { console.error( chalk.red( '⚠️ 恢复npm注册表地址失败,请手动执行: npm config set registry https://registry.npmjs.org' ) ) } } process.exit(1) } } // ==================== 基础抽象类 ==================== /** * 抽象注册表接口 */ class IRegistry { /** * 设置注册表地址 * @abstract * @param {string} registry */ setRegistry(registry) { throw new Error('Method must be implemented') } /** * 发布包 * @abstract * @param {PackageInfo} packageInfo */ publish(packageInfo) { throw new Error('Method must be implemented') } /** * 获取当前注册表地址 * @abstract * @returns {string} */ getCurrentRegistry() { throw new Error('Method must be implemented') } } // ==================== 注册表管理类 ==================== /** * npm注册表管理器 - 单一职责:管理npm注册表 * 使用npm原生命令管理注册表 */ class NpmRegistryManager extends IRegistry { constructor() { super() this.originalRegistry = null this.config = Config.getDefaultConfig() } /** * 设置npm注册表地址 * @param {string} registry */ setRegistry(registry) { try { execSync(`npm config set registry ${registry}`, { stdio: 'inherit' }) } catch (error) { throw new PublishError(`设置npm注册表地址失败: ${error.message}`, 'REGISTRY_ERROR', error) } } /** * 发布包到注册表 * @param {PackageInfo} packageInfo * @param {string} targetRegistry 目标注册表地址 * @param {boolean} isPrivateRegistry 是否是私有注册表 */ publish(packageInfo, targetRegistry = null, isPrivateRegistry = false) { try { const registry = targetRegistry || this.getCurrentRegistry() // 检查是否是预发布版本 const packageJson = packageInfo.readPackageJson() const { version } = packageJson const isPrerelease = version.includes('-') // 预发布版本包含连字符 let publishCommand = 'npm publish' // 如果是预发布版本,需要添加 --tag 参数 if (isPrerelease) { // 提取标签,例如从 1.0.0-alpha.1 中提取 alpha const [, prerelease] = version.split('-') const [tag] = prerelease.split('.') publishCommand += ` --tag ${tag}` } // 私服发布可能需要额外的参数 if (isPrivateRegistry) { // 可以在这里添加私服特定的参数 } execSync(publishCommand, { stdio: 'inherit', cwd: packageInfo.path, }) console.log(chalk.green(`✅ 发布到 ${registry} 完成`)) } catch (error) { throw new PublishError(`发布失败: ${error.message}`, 'PUBLISH_ERROR', error) } } /** * 获取当前注册表地址 * @returns {string} */ getCurrentRegistry() { try { const output = execSync('npm config get registry', { encoding: 'utf-8' }) return output.trim() } catch (error) { return this.config.publicRegistry } } /** * 保存当前注册表地址 */ saveOriginalRegistry() { this.originalRegistry = this.getCurrentRegistry() } /** * 恢复原始注册表地址 */ restoreOriginalRegistry() { if (this.originalRegistry) { this.setRegistry(this.originalRegistry) } } } // ==================== 包管理器类 ==================== /** * 包信息类 - 封装包的数据和行为 */ class PackageInfo { /** * @param {Object} data * @param {string} data.name * @param {string} data.path * @param {string} data.packagePath * @param {string} data.version */ constructor(data) { this.name = data.name this.path = data.path this.packagePath = data.packagePath this.version = data.version } /** * 读取package.json内容 * @returns {Object} */ readPackageJson() { try { const content = readFileSync(this.packagePath, 'utf-8') return JSON.parse(content) } catch (error) { throw new PublishError(`读取package.json失败: ${error.message}`, 'FILE_ERROR', error) } } /** * 写入package.json内容 * @param {Object} packageJson */ writePackageJson(packageJson) { try { const content = JSON.stringify(packageJson, null, 2) writeFileSync(this.packagePath, content, 'utf-8') } catch (error) { throw new PublishError(`写入package.json失败: ${error.message}`, 'FILE_ERROR', error) } } /** * 修改包名 * @param {string} fromScope * @param {string} toScope * @returns {string} 原始包名 */ changeScope(fromScope, toScope) { const packageJson = this.readPackageJson() const originalName = packageJson.name // 如果包名已经包含目标作用域,不需要修改 if (originalName.includes(toScope)) { return originalName } if (!originalName.includes(fromScope)) { throw new PublishError('包名不包含指定作用域,无法修改', 'SCOPE_ERROR') } packageJson.name = originalName.replace(fromScope, toScope) this.writePackageJson(packageJson) return originalName } /** * 恢复包名 * @param {string} originalName */ restoreScope(originalName) { const packageJson = this.readPackageJson() packageJson.name = originalName this.writePackageJson(packageJson) } } /** * 包发现器 - 单一职责:发现和管理包 */ class PackageDiscovery { /** * @param {Object} config */ constructor(config = Config.getDefaultConfig()) { this.config = config } /** * 获取packages目录下的所有包 * @returns {PackageInfo[]} */ getPackages() { const packages = [] try { const entries = readdirSync(this.config.packagesDir) for (const entry of entries) { const packagePath = join(this.config.packagesDir, entry) const stat = statSync(packagePath) if (this.isValidPackage(entry, stat)) { const packageJsonPath = join(packagePath, 'package.json') try { const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) if (this.isPublishablePackage(packageJson)) { packages.push( new PackageInfo({ name: packageJson.name, path: packagePath, packagePath: packageJsonPath, version: packageJson.version, }) ) } } catch (error) { // 跳过无效的包 } } } } catch (error) { throw new PublishError(`读取packages目录失败: ${error.message}`, 'DIRECTORY_ERROR', error) } return packages } /** * 检查是否为有效包目录 * @param {string} entry * @param {import('fs').Stats} stat * @returns {boolean} */ isValidPackage(entry, stat) { return stat.isDirectory() } /** * 检查包是否可发布 * @param {Object} packageJson * @returns {boolean} */ isPublishablePackage(packageJson) { return packageJson.name && !packageJson.private } } // ==================== 版本管理器类 ==================== /** * 版本管理器 - 单一职责:管理版本更新 */ class VersionManager { /** * 更新版本 * @param {PackageInfo} packageInfo * @param {string} versionType */ async updateVersion(packageInfo, versionType) { const packageJson = packageInfo.readPackageJson() const currentVersion = packageJson.version let newVersion switch (versionType) { case 'patch': newVersion = this.incrementVersion(currentVersion, 'patch') break case 'minor': newVersion = this.incrementVersion(currentVersion, 'minor') break case 'major': newVersion = this.incrementVersion(currentVersion, 'major') break case 'prerelease': newVersion = this.incrementPrerelease(currentVersion) break case 'prepatch': newVersion = `${this.incrementVersion(currentVersion, 'patch')}-alpha.1` break case 'preminor': newVersion = `${this.incrementVersion(currentVersion, 'minor')}-alpha.1` break case 'premajor': newVersion = `${this.incrementVersion(currentVersion, 'major')}-alpha.1` break default: throw new Error(`不支持的版本类型: ${versionType}`) } packageJson.version = newVersion packageInfo.writePackageJson(packageJson) } /** * 增加版本号 * @param {string} version * @param {string} type * @returns {string} */ incrementVersion(version, type) { // 移除预发布标识 const [baseVersion] = version.split('-') const versionParts = baseVersion.split('.').map(Number) switch (type) { case 'patch': versionParts[2]++ break case 'minor': versionParts[1]++ versionParts[2] = 0 break case 'major': versionParts[0]++ versionParts[1] = 0 versionParts[2] = 0 break } return versionParts.join('.') } /** * 增加预发布版本号 * @param {string} version * @returns {string} */ incrementPrerelease(version) { if (version.includes('-')) { // 如果是预发布版本,递增预发布标识符 const [baseVersion, prerelease] = version.split('-') const prereleaseParts = prerelease.split('.') // 从后往前找最后一个数字部分 let i = prereleaseParts.length - 1 while (i >= 0 && isNaN(prereleaseParts[i])) { i-- } if (i >= 0) { // 找到了数字部分,递增它 prereleaseParts[i] = (parseInt(prereleaseParts[i]) + 1).toString() // 重置后续的数字部分为0(如果有的话) for (let j = i + 1; j < prereleaseParts.length; j++) { if (!isNaN(prereleaseParts[j])) { prereleaseParts[j] = '0' } } } else { // 没有找到数字部分,追加.1 prereleaseParts.push('1') } return `${baseVersion}-${prereleaseParts.join('.')}` } else { // 如果不是预发布版本,则创建预发布版本 (alpha.1) return `${version}-alpha.1` } } } // ==================== 构建管理器类 ==================== /** * 构建管理器 - 单一职责:管理构建过程 */ class BuildManager { /** * 构建项目 - 只构建选中的包 * @param {PackageInfo} packageInfo */ buildProject(packageInfo) { try { // 只构建选中的包 execSync('pnpm build', { stdio: 'inherit', cwd: packageInfo.path, }) } catch (error) { throw new PublishError(`构建失败: ${error.message}`, 'BUILD_ERROR', error) } } } // ==================== 用户交互管理类 ==================== /** * 用户交互管理器 - 单一职责:处理用户交互 */ class UserInteractionManager { /** * 用户选择发布的包 * @param {PackageInfo[]} packages * @returns {Promise<PackageInfo|null>} */ async selectPackage(packages) { if (packages.length === 0) { console.log(chalk.red('❌ 没有找到可发布的包')) return null } const choices = packages.map(pkg => ({ title: pkg.name, value: pkg, })) const result = await prompts({ type: 'select', name: 'selectedPackage', message: '请选择要发布的包:', choices, initial: 0, }) return result.selectedPackage } /** * 用户选择版本更新类型 * @returns {Promise<string|null>} */ async selectVersionType() { const result = await prompts({ type: 'select', name: 'versionType', message: '请选择版本更新层级:', choices: [ { title: 'patch (补丁版本,如 1.0.0 -> 1.0.1)', value: VersionType.PATCH }, { title: 'minor (次版本,如 1.0.0 -> 1.1.0)', value: VersionType.MINOR }, { title: 'major (主版本,如 1.0.0 -> 2.0.0)', value: VersionType.MAJOR }, { title: 'prerelease (预发布版本)', value: VersionType.PRERELEASE }, { title: 'prepatch (预发布补丁版本)', value: VersionType.PREPATCH }, { title: 'preminor (预发布次版本)', value: VersionType.PREMINOR }, { title: 'premajor (预发布主版本)', value: VersionType.PREMAJOR }, ], initial: 0, }) return result.versionType } /** * 用户选择发布目标 * @returns {Promise<string[]>} */ async selectPublishTargets() { const result = await prompts({ type: 'multiselect', name: 'targets', message: '请选择发布目标:', choices: [ { title: '公服', value: 'public', selected: true }, { title: '私服', value: 'private', selected: true }, ], instructions: false, }) return result.targets || [] } } // ==================== 作用域替换管理器类 ==================== /** * 作用域替换管理器 - 单一职责:管理包作用域的全局替换和还原 */ class ScopeReplacementManager { /** * 替换包作用域 * @param {PackageInfo} packageInfo * @param {string} fromScope * @param {string} toScope */ replaceScopeInPackage(packageInfo, fromScope, toScope) { console.log(chalk.blue('🔍 正在替换作用域...')) // 存储被修改的文件路径,用于后续还原 const modifiedFiles = [] try { // 1. 替换 package.json 中的包名 const originalName = this.replacePackageName(packageInfo, fromScope, toScope) if (originalName) { modifiedFiles.push(packageInfo.packagePath) } // 2. 遍历包目录中的所有文件,替换作用域引用 const result = this.replaceScopeInDirectory( packageInfo.path, fromScope, toScope, modifiedFiles ) console.log(chalk.green(`✅ 作用域替换完成 (替换文件: ${modifiedFiles.length})`)) return modifiedFiles } catch (error) { // 如果替换过程中出现错误,尝试还原已做的更改 console.log(chalk.red(`❌ 作用域替换过程中出现错误: ${error.message}`)) this.restoreModifiedFiles(modifiedFiles, fromScope, toScope) throw new PublishError(`作用域替换失败: ${error.message}`, 'SCOPE_REPLACEMENT_ERROR', error) } } /** * 替换 package.json 中的包名 * @param {PackageInfo} packageInfo * @param {string} fromScope * @param {string} toScope * @returns {string|null} 原始包名,如果未修改则返回 null */ replacePackageName(packageInfo, fromScope, toScope) { const packageJson = packageInfo.readPackageJson() const originalName = packageJson.name // 如果包名已经包含目标作用域,不需要修改 if (originalName.includes(toScope)) { return null } if (!originalName.includes(fromScope)) { throw new PublishError(`包名不包含指定作用域: ${originalName}`, 'SCOPE_ERROR') } packageJson.name = originalName.replace(fromScope, toScope) packageInfo.writePackageJson(packageJson) return originalName } /** * 遍历目录中的文件,替换作用域引用 * @param {string} dirPath * @param {string} fromScope * @param {string} toScope * @param {string[]} modifiedFiles * @returns {Object} 处理结果统计 */ replaceScopeInDirectory(dirPath, fromScope, toScope, modifiedFiles) { // 忽略的目录和文件模式 const ignorePatterns = ['node_modules', 'dist', '.git', '.DS_Store', '~', 'cache'] const isIgnored = filePath => { // 检查是否包含忽略模式 if (ignorePatterns.some(pattern => filePath.includes(pattern))) { return true } // 特别检查node_modules目录 const normalizedPath = filePath.replace(/\\/g, '/') if (normalizedPath.includes('/node_modules/') || normalizedPath.endsWith('/node_modules')) { return true } return false } let processedFiles = 0 let replacedFiles = 0 const processDirectory = currentPath => { const entries = readdirSync(currentPath) for (const entry of entries) { const fullPath = join(currentPath, entry) // 跳过忽略的文件和目录 if (isIgnored(fullPath)) { continue } const stat = statSync(fullPath) if (stat.isDirectory()) { processDirectory(fullPath) } else if (stat.isFile()) { const result = this.replaceScopeInFile(fullPath, fromScope, toScope, modifiedFiles) processedFiles++ if (result) { replacedFiles++ } } } } processDirectory(dirPath) return { processedFiles, replacedFiles } } /** * 替换单个文件中的作用域引用 * @param {string} filePath * @param {string} fromScope * @param {string} toScope * @param {string[]} modifiedFiles * @returns {boolean} 是否进行了替换 */ replaceScopeInFile(filePath, fromScope, toScope, modifiedFiles) { // 只处理文本文件 const textFileExtensions = [ '.js', '.ts', '.json', '.md', '.txt', '.vue', '.jsx', '.tsx', '.html', '.css', '.scss', '.sass', ] const ext = extname(filePath).toLowerCase() if (!textFileExtensions.includes(ext)) { return false } try { const content = readFileSync(filePath, 'utf-8') // 检查文件是否包含要替换的作用域 if (content.includes(fromScope)) { const newContent = content.replace(new RegExp(fromScope, 'g'), toScope) // 只有当内容实际发生变化时才写入文件 if (content !== newContent) { writeFileSync(filePath, newContent, 'utf-8') modifiedFiles.push(filePath) return true } } return false } catch (error) { // 读取或写入文件失败时忽略错误,继续处理其他文件 return false } } /** * 还原被修改的文件 * @param {string[]} modifiedFiles * @param {string} fromScope * @param {string} toScope */ restoreModifiedFiles(modifiedFiles, fromScope, toScope) { if (modifiedFiles.length === 0) { return } console.log(chalk.blue('↩️ 正在还原更改...')) let restoredFiles = 0 for (const filePath of modifiedFiles) { try { const content = readFileSync(filePath, 'utf-8') // 还原逻辑:将文件中当前的 fromScope 替换为 toScope const originalContent = content.replace(new RegExp(fromScope, 'g'), toScope) writeFileSync(filePath, originalContent, 'utf-8') restoredFiles++ } catch (error) { // 忽略还原错误 } } console.log(chalk.green(`✅ 更改还原完成 (成功还原: ${restoredFiles}/${modifiedFiles.length})`)) } } // ==================== 发布策略接口和实现 ==================== /** * 抽象发布策略接口 */ class IPublishStrategy { /** * 执行发布策略 * @abstract * @param {PackageInfo} packageInfo * @param {IRegistry} registryManager */ async execute(packageInfo, registryManager) { throw new Error('Method must be implemented') } } /** * 公服发布策略 */ class PublicPublishStrategy extends IPublishStrategy { async execute(packageInfo, registryManager) { const config = Config.getDefaultConfig() registryManager.setRegistry(config.publicRegistry) registryManager.publish(packageInfo, config.publicRegistry) } } /** * 私服发布策略 */ class PrivatePublishStrategy extends IPublishStrategy { async execute(packageInfo, registryManager) { const config = Config.getDefaultConfig() const scopeReplacementManager = new ScopeReplacementManager() let modifiedFiles = [] try { // 1. 替换作用域 modifiedFiles = scopeReplacementManager.replaceScopeInPackage( packageInfo, config.publicScope, config.privateScope ) // 2. 重新构建项目 console.log(chalk.blue('🏗️ 正在构建项目...')) const buildManager = new BuildManager() buildManager.buildProject(packageInfo) console.log(chalk.green('✅ 项目构建完成')) // 3. 发布到私服 console.log(chalk.blue('📦 正在发布到私服...')) registryManager.setRegistry(config.privateRegistry) registryManager.publish(packageInfo, config.privateRegistry, true) // true表示是私服发布 console.log(chalk.green('✅ 私服发布完成')) } catch (error) { console.log(chalk.red(`❌ 私服发布失败: ${error.message}`)) throw error } finally { // 4. 无论如何都要还原作用域 scopeReplacementManager.restoreModifiedFiles( modifiedFiles, config.privateScope, // fromScope - 当前文件中的内容 (@privateScope) config.publicScope // toScope - 要还原成的内容 (@publicScope) ) } } } // ==================== 工厂类 ==================== /** * 发布策略工厂 - 工厂模式创建发布策略 */ class PublishStrategyFactory { /** * 创建发布策略 * @param {string} type * @returns {IPublishStrategy} */ static createStrategy(type) { switch (type) { case 'public': return new PublicPublishStrategy() case 'private': return new PrivatePublishStrategy() default: throw new PublishError(`未知的发布策略类型: ${type}`, 'STRATEGY_ERROR') } } } // ==================== 主编排器类 ==================== /** * npm双仓发布编排器 - 主编排器,协调各个组件 */ class DualPublishOrchestrator { /** * @param {Object} config */ constructor(config = Config.getDefaultConfig()) { this.config = config this.registryManager = new NpmRegistryManager() this.packageDiscovery = new PackageDiscovery(config) this.versionManager = new VersionManager() this.buildManager = new BuildManager() this.userInteraction = new UserInteractionManager() } /** * 执行完整的发布流程 * 1. 检索packages文件夹下的子项目 * 2. 用户选择需要发布的包 * 3. 用户选择版本更新类型 * 4. 用户选择发布仓库地址(公服、私服、或者公服+私服) * 5. 版本更新 * 6. 构建 * 7. 发布 */ async execute() { try { console.log(chalk.cyan('🚀 开始npm发布流程\n')) // 1. 检索packages文件夹下的子项目,作为发布待选包 const packages = await this.discoverPackages() // 2. 选择需要发布的包(单选,可以不选->退出流程) const selectedPackage = await this.userInteraction.selectPackage(packages) if (!selectedPackage) return this.exitGracefully('取消发布') // 3. 选择版本更新类型 const versionType = await this.userInteraction.selectVersionType() if (!versionType) return this.exitGracefully('取消发布') // 4. 选择发布仓库地址(公服、私服、或者公服+私服) const publishTargets = await this.userInteraction.selectPublishTargets() if (publishTargets.length === 0) return this.exitGracefully('取消发布') // 5-7. 执行发布流程 await this.performPublishWorkflow(selectedPackage, versionType, publishTargets) // 完成发布 this.completePublication(selectedPackage, versionType, publishTargets) } catch (error) { ErrorHandler.handleError(error, this.registryManager) } } /** * 发现可发布的包 * @returns {Promise<PackageInfo[]>} */ async discoverPackages() { const packages = this.packageDiscovery.getPackages() if (packages.length === 0) { throw new PublishError('没有找到可发布的包', 'NO_PACKAGES') } return packages } /** * 执行发布工作流程 * 5. 版本更新:使用用户选择的版本更新类型,对用户选择的需要发布的包进行版本号更新 * 6. 构建:构建用户选择的需要发布的包 * 7. 发布: * 7.1 查询系统当前npm地址,并记录 * 7.2 用户选择向npm公服发布:如果当前npm地址不是公服,则将npm地址切换到公服;发布包 * 7.3 用户选择向npm私服发布:修改包名(将@publicScope@privateScope);如果当前npm地址不是用户需要发布的私服地址,则切换npm地址;发布包;还原包名 * 7.4 如果切换了npm地址,还原系统npm地址 * @param {PackageInfo} selectedPackage * @param {string} versionType * @param {string[]} publishTargets */ async performPublishWorkflow(selectedPackage, versionType, publishTargets) { // 5. 版本更新:使用用户选择的版本更新类型,对用户选择的需要发布的包进行版本号更新 await this.versionManager.updateVersion(selectedPackage, versionType) // 7.1 查询系统当前npm地址,并记录 this.registryManager.saveOriginalRegistry() // 7.2 用户选择向npm公服发布:如果当前npm地址不是公服,则将npm地址切换到公服;发布包 if (publishTargets.includes('public')) { // 只有在发布公服时才构建 console.log(chalk.blue('🏗️ 正在构建项目...')) this.buildManager.buildProject(selectedPackage) console.log(chalk.green('✅ 项目构建完成')) console.log(chalk.blue('📦 发布到公服...')) const publicStrategy = PublishStrategyFactory.createStrategy('public') await publicStrategy.execute(selectedPackage, this.registryManager) console.log(chalk.green('✅ 公服发布完成')) } // 7.3 用户选择向npm私服发布:修改包名(将@publicScope@privateScope);如果当前npm地址不是用户需要发布的私服地址,则切换npm地址;发布包;还原包名 if (publishTargets.includes('private')) { console.log(chalk.blue('📦 发布到私服...')) const privateStrategy = PublishStrategyFactory.createStrategy('private') await privateStrategy.execute(selectedPackage, this.registryManager) console.log(chalk.green('✅ 私服发布完成')) } // 7.4 如果切换了npm地址,还原系统npm地址 this.registryManager.restoreOriginalRegistry() } /** * 完成发布流程 * @param {PackageInfo} packageInfo * @param {string} versionType * @param {string[]} publishTargets */ completePublication(packageInfo, versionType, publishTargets) { console.log(chalk.green('\n🎉 npm发布完成!')) console.log(chalk.green(`📦 包名: ${packageInfo.name}`)) console.log(chalk.green(`📦 版本类型: ${versionType}`)) if (publishTargets.includes('public')) { console.log(chalk.green('📦 公服发布: ✅ 完成')) } if (publishTargets.includes('private')) { console.log(chalk.green('📦 私服发布: ✅ 完成')) } } /** * 优雅退出 * @param {string} message */ exitGracefully(message) { console.log(chalk.yellow(`👋 ${message},退出流程`)) process.exit(0) } } // ==================== 主入口 ==================== /** * 主函数 - 使用工厂模式创建编排器 */ function main() { const config = Config.getDefaultConfig() const orchestrator = new DualPublishOrchestrator(config) orchestrator.execute() } // 启动应用 main()