From e6de3eeef340d50b48b199d2c47e10ca88f216da Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Fri, 22 Aug 2025 10:09:37 +0800 Subject: [PATCH] Enhance upload commands to support custom versioning and update documentation. Added version option to uploadIpa, uploadApk, and uploadApp commands. Updated README and localization files to reflect changes. --- .gitignore | 5 +- README.md | 177 +++++++++++++----------- README.zh-CN.md | 235 ++++++++++++++++++-------------- cli.json | 24 +++- example/USAGE_CUSTOM_VERSION.md | 53 +++++++ src/locales/en.ts | 1 + src/locales/zh.ts | 1 + src/package.ts | 75 ++++++---- src/provider.ts | 2 +- src/types.ts | 1 + 10 files changed, 361 insertions(+), 213 deletions(-) create mode 100644 example/USAGE_CUSTOM_VERSION.md diff --git a/.gitignore b/.gitignore index 4a00a28..0fe487f 100644 --- a/.gitignore +++ b/.gitignore @@ -104,4 +104,7 @@ dist .tern-port lib/ -.DS_Store \ No newline at end of file +.DS_Store +# react-native-update +.update +.pushy \ No newline at end of file diff --git a/README.md b/README.md index 0fe2325..26473d3 100644 --- a/README.md +++ b/README.md @@ -49,14 +49,14 @@ const provider = moduleManager.getProvider(); const bundleResult = await provider.bundle({ platform: 'ios', dev: false, - sourcemap: true + sourcemap: true, }); // Publish version const publishResult = await provider.publish({ name: 'v1.2.3', description: 'Bug fixes and improvements', - rollout: 100 + rollout: 100, }); ``` @@ -65,12 +65,16 @@ const publishResult = await provider.publish({ ### 1. Define Module ```typescript -import type { CLIModule, CommandDefinition, CustomWorkflow } from 'react-native-update-cli'; +import type { + CLIModule, + CommandDefinition, + CustomWorkflow, +} from 'react-native-update-cli'; export const myCustomModule: CLIModule = { name: 'my-custom', version: '1.0.0', - + commands: [ { name: 'custom-command', @@ -79,15 +83,15 @@ export const myCustomModule: CLIModule = { console.log('Executing custom command...'); return { success: true, - data: { message: 'Custom command executed' } + data: { message: 'Custom command executed' }, }; }, options: { - param: { hasValue: true, description: 'Custom parameter' } - } - } + param: { hasValue: true, description: 'Custom parameter' }, + }, + }, ], - + workflows: [ { name: 'my-workflow', @@ -99,7 +103,7 @@ export const myCustomModule: CLIModule = { execute: async (context, previousResult) => { console.log('Executing step 1...'); return { step1Completed: true }; - } + }, }, { name: 'step2', @@ -107,19 +111,19 @@ export const myCustomModule: CLIModule = { execute: async (context, previousResult) => { console.log('Executing step 2...'); return { ...previousResult, step2Completed: true }; - } - } - ] - } + }, + }, + ], + }, ], - + init: (provider) => { console.log('Custom module initialized'); }, - + cleanup: () => { console.log('Custom module cleanup'); - } + }, }; ``` @@ -135,13 +139,13 @@ moduleManager.registerModule(myCustomModule); // Execute custom command const result = await moduleManager.executeCommand('custom-command', { args: [], - options: { param: 'value' } + options: { param: 'value' }, }); // Execute custom workflow const workflowResult = await moduleManager.executeWorkflow('my-workflow', { args: [], - options: {} + options: {}, }); ``` @@ -191,6 +195,7 @@ Each workflow step contains: ## 📋 Built-in Modules ### Bundle Module (`bundle`) + - `bundle`: Bundle JavaScript code and optionally publish - `diff`: Generate differences between two PPK files - `hdiff`: Generate hdiff between two PPK files @@ -201,27 +206,31 @@ Each workflow step contains: - `hdiffFromIpa`: Generate hdiff from IPA files ### Version Module (`version`) + - `publish`: Publish new version - `versions`: List all versions - `update`: Update version information - `updateVersionInfo`: Update version metadata ### App Module (`app`) + - `createApp`: Create new application - `apps`: List all applications - `selectApp`: Select application - `deleteApp`: Delete application ### Package Module (`package`) -- `uploadIpa`: Upload IPA files -- `uploadApk`: Upload APK files -- `uploadApp`: Upload APP files + +- `uploadIpa`: Upload IPA files (supports `--version` to override extracted version) +- `uploadApk`: Upload APK files (supports `--version` to override extracted version) +- `uploadApp`: Upload APP files (supports `--version` to override extracted version) - `parseApp`: Parse APP file information - `parseIpa`: Parse IPA file information - `parseApk`: Parse APK file information - `packages`: List packages ### User Module (`user`) + - `login`: Login - `logout`: Logout - `me`: Show user information @@ -234,36 +243,45 @@ Each workflow step contains: interface CLIProvider { // Bundle bundle(options: BundleOptions): Promise; - + // Publish publish(options: PublishOptions): Promise; - + // Upload upload(options: UploadOptions): Promise; - + // Application management - getSelectedApp(platform?: Platform): Promise<{ appId: string; platform: Platform }>; + getSelectedApp( + platform?: Platform, + ): Promise<{ appId: string; platform: Platform }>; listApps(platform?: Platform): Promise; createApp(name: string, platform: Platform): Promise; - + // Version management listVersions(appId: string): Promise; getVersion(appId: string, versionId: string): Promise; - updateVersion(appId: string, versionId: string, updates: Partial): Promise; - + updateVersion( + appId: string, + versionId: string, + updates: Partial, + ): Promise; + // Package management listPackages(appId: string, platform?: Platform): Promise; getPackage(appId: string, packageId: string): Promise; - + // Utility functions getPlatform(platform?: Platform): Promise; loadSession(): Promise; saveToLocal(key: string, value: string): void; question(prompt: string): Promise; - + // Workflows registerWorkflow(workflow: CustomWorkflow): void; - executeWorkflow(workflowName: string, context: CommandContext): Promise; + executeWorkflow( + workflowName: string, + context: CommandContext, + ): Promise; } ``` @@ -276,8 +294,8 @@ const bundleResult = await moduleManager.executeCommand('custom-bundle', { options: { platform: 'android', validate: true, - optimize: true - } + optimize: true, + }, }); // Generate diff file @@ -286,8 +304,8 @@ const diffResult = await moduleManager.executeCommand('diff', { options: { origin: './build/v1.0.0.ppk', next: './build/v1.1.0.ppk', - output: './build/diff.patch' - } + output: './build/diff.patch', + }, }); // Generate diff from APK files @@ -296,8 +314,8 @@ const apkDiffResult = await moduleManager.executeCommand('diffFromApk', { options: { origin: './build/app-v1.0.0.apk', next: './build/app-v1.1.0.apk', - output: './build/apk-diff.patch' - } + output: './build/apk-diff.patch', + }, }); ``` @@ -349,29 +367,31 @@ Provider provides a concise programming interface suitable for integrating React ### 📋 Core API Methods #### Core Business Functions + ```typescript // Bundle application await provider.bundle({ platform: 'ios', dev: false, - sourcemap: true + sourcemap: true, }); // Publish version await provider.publish({ name: 'v1.0.0', description: 'Bug fixes', - rollout: 100 + rollout: 100, }); // Upload file await provider.upload({ filePath: 'app.ipa', - platform: 'ios' + platform: 'ios', }); ``` #### Application Management + ```typescript // Create application await provider.createApp('MyApp', 'ios'); @@ -384,6 +404,7 @@ const { appId, platform } = await provider.getSelectedApp('ios'); ``` #### Version Management + ```typescript // List versions await provider.listVersions('app123'); @@ -391,11 +412,12 @@ await provider.listVersions('app123'); // Update version await provider.updateVersion('app123', 'version456', { name: 'v1.1.0', - description: 'New features' + description: 'New features', }); ``` #### Utility Functions + ```typescript // Get platform const platform = await provider.getPlatform('ios'); @@ -407,72 +429,75 @@ const session = await provider.loadSession(); ### 🎯 Use Cases #### 1. Automated Build Scripts + ```typescript import { moduleManager } from 'react-native-update-cli'; async function buildAndPublish() { const provider = moduleManager.getProvider(); - + // 1. Bundle const bundleResult = await provider.bundle({ platform: 'ios', dev: false, - sourcemap: true + sourcemap: true, }); - + if (!bundleResult.success) { throw new Error(`Bundle failed: ${bundleResult.error}`); } - + // 2. Publish const publishResult = await provider.publish({ name: 'v1.2.3', description: 'Bug fixes and performance improvements', - rollout: 100 + rollout: 100, }); - + if (!publishResult.success) { throw new Error(`Publish failed: ${publishResult.error}`); } - + console.log('Build and publish completed!'); } ``` #### 2. CI/CD Integration + ```typescript async function ciBuild() { const provider = moduleManager.getProvider(); - + const result = await provider.bundle({ platform: process.env.PLATFORM as 'ios' | 'android', dev: process.env.NODE_ENV !== 'production', - sourcemap: process.env.NODE_ENV === 'production' + sourcemap: process.env.NODE_ENV === 'production', }); - + return result; } ``` #### 3. Application Management Service + ```typescript class AppManagementService { private provider = moduleManager.getProvider(); - + async setupNewApp(name: string, platform: Platform) { // Create application const createResult = await this.provider.createApp(name, platform); - + if (createResult.success) { // Get application information const { appId } = await this.provider.getSelectedApp(platform); - + // List versions await this.provider.listVersions(appId); - + return { appId, success: true }; } - + return { success: false, error: createResult.error }; } } @@ -488,6 +513,7 @@ class AppManagementService { ### 🔧 Advanced Features #### Custom Workflows + ```typescript // Register custom workflow provider.registerWorkflow({ @@ -498,7 +524,7 @@ provider.registerWorkflow({ name: 'bundle', execute: async () => { return await provider.bundle({ platform: 'ios', dev: false }); - } + }, }, { name: 'publish', @@ -507,9 +533,9 @@ provider.registerWorkflow({ throw new Error('Bundle failed, cannot publish'); } return await provider.publish({ name: 'auto-release', rollout: 50 }); - } - } - ] + }, + }, + ], }); // Execute workflow @@ -523,50 +549,49 @@ import { moduleManager } from 'react-native-update-cli'; class ReactNativeUpdateService { private provider = moduleManager.getProvider(); - + async initialize() { // Load session await this.provider.loadSession(); } - + async buildAndDeploy(platform: Platform, version: string) { try { // 1. Bundle const bundleResult = await this.provider.bundle({ platform, dev: false, - sourcemap: true + sourcemap: true, }); - + if (!bundleResult.success) { throw new Error(`Bundle failed: ${bundleResult.error}`); } - + // 2. Publish const publishResult = await this.provider.publish({ name: version, description: `Release ${version}`, - rollout: 100 + rollout: 100, }); - + if (!publishResult.success) { throw new Error(`Publish failed: ${publishResult.error}`); } - + return { success: true, data: publishResult.data }; - } catch (error) { - return { - success: false, - error: error instanceof Error ? error.message : 'Unknown error' + return { + success: false, + error: error instanceof Error ? error.message : 'Unknown error', }; } } - + async getAppInfo(platform: Platform) { const { appId } = await this.provider.getSelectedApp(platform); const versions = await this.provider.listVersions(appId); - + return { appId, versions }; } } @@ -575,4 +600,4 @@ class ReactNativeUpdateService { const service = new ReactNativeUpdateService(); await service.initialize(); await service.buildAndDeploy('ios', 'v1.0.0'); -``` \ No newline at end of file +``` diff --git a/README.zh-CN.md b/README.zh-CN.md index 6b3b141..a004a47 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,15 +1,15 @@ # React Native Update CLI -这是一个统一的React Native Update CLI,同时支持传统命令和模块化架构以及自定义发布流程。 +这是一个统一的 React Native Update CLI,同时支持传统命令和模块化架构以及自定义发布流程。 ## 🚀 特性 -- **统一CLI**: 使用单个`pushy`命令提供所有功能 +- **统一 CLI**: 使用单个`pushy`命令提供所有功能 - **向后兼容**: 所有现有命令都能正常工作 -- **模块化架构**: 将CLI功能拆分为独立的模块 +- **模块化架构**: 将 CLI 功能拆分为独立的模块 - **自定义工作流**: 支持创建自定义的发布流程 - **可扩展性**: 用户可以导入和注册自定义模块 -- **类型安全**: 完整的TypeScript类型支持 +- **类型安全**: 完整的 TypeScript 类型支持 ## 📦 安装 @@ -47,14 +47,14 @@ const provider = moduleManager.getProvider(); const bundleResult = await provider.bundle({ platform: 'ios', dev: false, - sourcemap: true + sourcemap: true, }); // 发布版本 const publishResult = await provider.publish({ name: 'v1.2.3', description: 'Bug fixes and improvements', - rollout: 100 + rollout: 100, }); ``` @@ -63,12 +63,16 @@ const publishResult = await provider.publish({ ### 1. 定义模块 ```typescript -import type { CLIModule, CommandDefinition, CustomWorkflow } from 'react-native-update-cli'; +import type { + CLIModule, + CommandDefinition, + CustomWorkflow, +} from 'react-native-update-cli'; export const myCustomModule: CLIModule = { name: 'my-custom', version: '1.0.0', - + commands: [ { name: 'custom-command', @@ -77,15 +81,15 @@ export const myCustomModule: CLIModule = { console.log('Executing custom command...'); return { success: true, - data: { message: 'Custom command executed' } + data: { message: 'Custom command executed' }, }; }, options: { - param: { hasValue: true, description: 'Custom parameter' } - } - } + param: { hasValue: true, description: 'Custom parameter' }, + }, + }, ], - + workflows: [ { name: 'my-workflow', @@ -97,7 +101,7 @@ export const myCustomModule: CLIModule = { execute: async (context, previousResult) => { console.log('Executing step 1...'); return { step1Completed: true }; - } + }, }, { name: 'step2', @@ -105,19 +109,19 @@ export const myCustomModule: CLIModule = { execute: async (context, previousResult) => { console.log('Executing step 2...'); return { ...previousResult, step2Completed: true }; - } - } - ] - } + }, + }, + ], + }, ], - + init: (provider) => { console.log('Custom module initialized'); }, - + cleanup: () => { console.log('Custom module cleanup'); - } + }, }; ``` @@ -133,13 +137,13 @@ moduleManager.registerModule(myCustomModule); // 执行自定义命令 const result = await moduleManager.executeCommand('custom-command', { args: [], - options: { param: 'value' } + options: { param: 'value' }, }); // 执行自定义工作流 const workflowResult = await moduleManager.executeWorkflow('my-workflow', { args: [], - options: {} + options: {}, }); ``` @@ -188,43 +192,48 @@ const workflowResult = await moduleManager.executeWorkflow('my-workflow', { ## 📋 内置模块 -### Bundle模块 (`bundle`) -- `bundle`: 打包JavaScript代码并可选发布 -- `diff`: 生成两个PPK文件之间的差异 -- `hdiff`: 生成两个PPK文件之间的hdiff -- `diffFromApk`: 从APK文件生成差异 -- `hdiffFromApk`: 从APK文件生成hdiff -- `hdiffFromApp`: 从APP文件生成hdiff -- `diffFromIpa`: 从IPA文件生成差异 -- `hdiffFromIpa`: 从IPA文件生成hdiff +### Bundle 模块 (`bundle`) + +- `bundle`: 打包 JavaScript 代码并可选发布 +- `diff`: 生成两个 PPK 文件之间的差异 +- `hdiff`: 生成两个 PPK 文件之间的 hdiff +- `diffFromApk`: 从 APK 文件生成差异 +- `hdiffFromApk`: 从 APK 文件生成 hdiff +- `hdiffFromApp`: 从 APP 文件生成 hdiff +- `diffFromIpa`: 从 IPA 文件生成差异 +- `hdiffFromIpa`: 从 IPA 文件生成 hdiff + +### Version 模块 (`version`) -### Version模块 (`version`) - `publish`: 发布新版本 - `versions`: 列出所有版本 - `update`: 更新版本信息 - `updateVersionInfo`: 更新版本元数据 -### App模块 (`app`) +### App 模块 (`app`) + - `createApp`: 创建新应用 - `apps`: 列出所有应用 - `selectApp`: 选择应用 - `deleteApp`: 删除应用 -### Package模块 (`package`) -- `uploadIpa`: 上传IPA文件 -- `uploadApk`: 上传APK文件 -- `uploadApp`: 上传APP文件 -- `parseApp`: 解析APP文件信息 -- `parseIpa`: 解析IPA文件信息 -- `parseApk`: 解析APK文件信息 +### Package 模块 (`package`) + +- `uploadIpa`: 上传 IPA 文件(支持 `--version` 参数覆盖提取的版本) +- `uploadApk`: 上传 APK 文件(支持 `--version` 参数覆盖提取的版本) +- `uploadApp`: 上传 APP 文件(支持 `--version` 参数覆盖提取的版本) +- `parseApp`: 解析 APP 文件信息 +- `parseIpa`: 解析 IPA 文件信息 +- `parseApk`: 解析 APK 文件信息 - `packages`: 列出包 -### User模块 (`user`) +### User 模块 (`user`) + - `login`: 登录 - `logout`: 登出 - `me`: 显示用户信息 -## 🛠️ CLI提供者API +## 🛠️ CLI 提供者 API ### 核心功能 @@ -232,36 +241,45 @@ const workflowResult = await moduleManager.executeWorkflow('my-workflow', { interface CLIProvider { // 打包 bundle(options: BundleOptions): Promise; - + // 发布 publish(options: PublishOptions): Promise; - + // 上传 upload(options: UploadOptions): Promise; - + // 应用管理 - getSelectedApp(platform?: Platform): Promise<{ appId: string; platform: Platform }>; + getSelectedApp( + platform?: Platform, + ): Promise<{ appId: string; platform: Platform }>; listApps(platform?: Platform): Promise; createApp(name: string, platform: Platform): Promise; - + // 版本管理 listVersions(appId: string): Promise; getVersion(appId: string, versionId: string): Promise; - updateVersion(appId: string, versionId: string, updates: Partial): Promise; - + updateVersion( + appId: string, + versionId: string, + updates: Partial, + ): Promise; + // 包管理 listPackages(appId: string, platform?: Platform): Promise; getPackage(appId: string, packageId: string): Promise; - + // 工具函数 getPlatform(platform?: Platform): Promise; loadSession(): Promise; saveToLocal(key: string, value: string): void; question(prompt: string): Promise; - + // 工作流 registerWorkflow(workflow: CustomWorkflow): void; - executeWorkflow(workflowName: string, context: CommandContext): Promise; + executeWorkflow( + workflowName: string, + context: CommandContext, + ): Promise; } ``` @@ -274,8 +292,8 @@ const bundleResult = await moduleManager.executeCommand('custom-bundle', { options: { platform: 'android', validate: true, - optimize: true - } + optimize: true, + }, }); // 生成差异文件 @@ -284,8 +302,8 @@ const diffResult = await moduleManager.executeCommand('diff', { options: { origin: './build/v1.0.0.ppk', next: './build/v1.1.0.ppk', - output: './build/diff.patch' - } + output: './build/diff.patch', + }, }); // 从APK文件生成差异 @@ -294,8 +312,8 @@ const apkDiffResult = await moduleManager.executeCommand('diffFromApk', { options: { origin: './build/app-v1.0.0.apk', next: './build/app-v1.1.0.apk', - output: './build/apk-diff.patch' - } + output: './build/apk-diff.patch', + }, }); ``` @@ -330,46 +348,48 @@ export NO_INTERACTIVE=true ## 🚨 注意事项 -1. **向后兼容**: 新的模块化CLI保持与现有CLI的兼容性 -2. **类型安全**: 所有API都有完整的TypeScript类型定义 +1. **向后兼容**: 新的模块化 CLI 保持与现有 CLI 的兼容性 +2. **类型安全**: 所有 API 都有完整的 TypeScript 类型定义 3. **错误处理**: 所有操作都返回标准化的结果格式 4. **资源清理**: 模块支持清理函数来释放资源 5. **模块分离**: 功能按逻辑分离到不同模块中,便于维护和扩展 ## 🤝 贡献 -欢迎提交Issue和Pull Request来改进这个项目! +欢迎提交 Issue 和 Pull Request 来改进这个项目! ## 🚀 Provider API 使用指南 -Provider提供了简洁的编程接口,适合在应用程序中集成React Native Update CLI功能。 +Provider 提供了简洁的编程接口,适合在应用程序中集成 React Native Update CLI 功能。 -### 📋 核心API方法 +### 📋 核心 API 方法 #### 核心业务功能 + ```typescript // 打包应用 await provider.bundle({ platform: 'ios', dev: false, - sourcemap: true + sourcemap: true, }); // 发布版本 await provider.publish({ name: 'v1.0.0', description: 'Bug fixes', - rollout: 100 + rollout: 100, }); // 上传文件 await provider.upload({ filePath: 'app.ipa', - platform: 'ios' + platform: 'ios', }); ``` #### 应用管理 + ```typescript // 创建应用 await provider.createApp('MyApp', 'ios'); @@ -382,6 +402,7 @@ const { appId, platform } = await provider.getSelectedApp('ios'); ``` #### 版本管理 + ```typescript // 列出版本 await provider.listVersions('app123'); @@ -389,11 +410,12 @@ await provider.listVersions('app123'); // 更新版本 await provider.updateVersion('app123', 'version456', { name: 'v1.1.0', - description: 'New features' + description: 'New features', }); ``` #### 工具函数 + ```typescript // 获取平台 const platform = await provider.getPlatform('ios'); @@ -405,72 +427,75 @@ const session = await provider.loadSession(); ### 🎯 使用场景 #### 1. 自动化构建脚本 + ```typescript import { moduleManager } from 'react-native-update-cli'; async function buildAndPublish() { const provider = moduleManager.getProvider(); - + // 1. 打包 const bundleResult = await provider.bundle({ platform: 'ios', dev: false, - sourcemap: true + sourcemap: true, }); - + if (!bundleResult.success) { throw new Error(`打包失败: ${bundleResult.error}`); } - + // 2. 发布 const publishResult = await provider.publish({ name: 'v1.2.3', description: 'Bug fixes and performance improvements', - rollout: 100 + rollout: 100, }); - + if (!publishResult.success) { throw new Error(`发布失败: ${publishResult.error}`); } - + console.log('构建和发布完成!'); } ``` -#### 2. CI/CD集成 +#### 2. CI/CD 集成 + ```typescript async function ciBuild() { const provider = moduleManager.getProvider(); - + const result = await provider.bundle({ platform: process.env.PLATFORM as 'ios' | 'android', dev: process.env.NODE_ENV !== 'production', - sourcemap: process.env.NODE_ENV === 'production' + sourcemap: process.env.NODE_ENV === 'production', }); - + return result; } ``` #### 3. 应用管理服务 + ```typescript class AppManagementService { private provider = moduleManager.getProvider(); - + async setupNewApp(name: string, platform: Platform) { // 创建应用 const createResult = await this.provider.createApp(name, platform); - + if (createResult.success) { // 获取应用信息 const { appId } = await this.provider.getSelectedApp(platform); - + // 列出版本 await this.provider.listVersions(appId); - + return { appId, success: true }; } - + return { success: false, error: createResult.error }; } } @@ -478,14 +503,15 @@ class AppManagementService { ### ⚠️ 注意事项 -1. **错误处理**: 所有Provider方法都返回`CommandResult`,需要检查`success`字段 -2. **类型安全**: Provider提供完整的TypeScript类型支持 +1. **错误处理**: 所有 Provider 方法都返回`CommandResult`,需要检查`success`字段 +2. **类型安全**: Provider 提供完整的 TypeScript 类型支持 3. **会话管理**: 使用前确保已登录,可通过`loadSession()`检查 4. **平台支持**: 支持`'ios' | 'android' | 'harmony'`三个平台 ### 🔧 高级功能 #### 自定义工作流 + ```typescript // 注册自定义工作流 provider.registerWorkflow({ @@ -496,7 +522,7 @@ provider.registerWorkflow({ name: 'bundle', execute: async () => { return await provider.bundle({ platform: 'ios', dev: false }); - } + }, }, { name: 'publish', @@ -505,9 +531,9 @@ provider.registerWorkflow({ throw new Error('打包失败,无法发布'); } return await provider.publish({ name: 'auto-release', rollout: 50 }); - } - } - ] + }, + }, + ], }); // 执行工作流 @@ -521,50 +547,49 @@ import { moduleManager } from 'react-native-update-cli'; class ReactNativeUpdateService { private provider = moduleManager.getProvider(); - + async initialize() { // 加载会话 await this.provider.loadSession(); } - + async buildAndDeploy(platform: Platform, version: string) { try { // 1. 打包 const bundleResult = await this.provider.bundle({ platform, dev: false, - sourcemap: true + sourcemap: true, }); - + if (!bundleResult.success) { throw new Error(`打包失败: ${bundleResult.error}`); } - + // 2. 发布 const publishResult = await this.provider.publish({ name: version, description: `Release ${version}`, - rollout: 100 + rollout: 100, }); - + if (!publishResult.success) { throw new Error(`发布失败: ${publishResult.error}`); } - + return { success: true, data: publishResult.data }; - } catch (error) { - return { - success: false, - error: error instanceof Error ? error.message : 'Unknown error' + return { + success: false, + error: error instanceof Error ? error.message : 'Unknown error', }; } } - + async getAppInfo(platform: Platform) { const { appId } = await this.provider.getSelectedApp(platform); const versions = await this.provider.listVersions(appId); - + return { appId, versions }; } } @@ -573,4 +598,4 @@ class ReactNativeUpdateService { const service = new ReactNativeUpdateService(); await service.initialize(); await service.buildAndDeploy('ios', 'v1.0.0'); -``` \ No newline at end of file +``` diff --git a/cli.json b/cli.json index d9a7f8f..244a3c3 100644 --- a/cli.json +++ b/cli.json @@ -31,9 +31,27 @@ } } }, - "uploadIpa": {}, - "uploadApk": {}, - "uploadApp": {}, + "uploadIpa": { + "options": { + "version": { + "hasValue": true + } + } + }, + "uploadApk": { + "options": { + "version": { + "hasValue": true + } + } + }, + "uploadApp": { + "options": { + "version": { + "hasValue": true + } + } + }, "parseApp": {}, "parseIpa": {}, "parseApk": {}, diff --git a/example/USAGE_CUSTOM_VERSION.md b/example/USAGE_CUSTOM_VERSION.md new file mode 100644 index 0000000..d79cc82 --- /dev/null +++ b/example/USAGE_CUSTOM_VERSION.md @@ -0,0 +1,53 @@ +# Custom Version Parameter Usage + +This document demonstrates how to use the `--version` parameter with upload commands to override the version extracted from APK/IPA/APP files. + +## Commands Supporting Custom Version + +- `uploadApk --version ` +- `uploadIpa --version ` +- `uploadApp --version ` + +## Usage Examples + +### Upload APK with Custom Version + +```bash +# Upload APK and override version to "1.2.3-custom" +cresc uploadApk app-release.apk --version "1.2.3-custom" +``` + +### Upload IPA with Custom Version + +```bash +# Upload IPA and override version to "2.0.0-beta" +cresc uploadIpa MyApp.ipa --version "2.0.0-beta" +``` + +### Upload APP with Custom Version + +```bash +# Upload APP and override version to "3.1.0-harmony" +cresc uploadApp MyApp.app --version "3.1.0-harmony" +``` + +## Behavior + +1. **Without `--version`**: The command uses the version extracted from the package file (APK/IPA/APP) +2. **With `--version`**: The command uses the provided custom version instead of the extracted version +3. **Console Output**: When using a custom version, the CLI will display "Using custom version: " message + +## Use Cases + +- **Testing**: Upload test builds with specific version identifiers +- **Hotfixes**: Override version numbers for emergency releases +- **Version Alignment**: Ensure consistent versioning across different platforms +- **Manual Override**: When the extracted version is incorrect or inappropriate + +## Example Output + +``` +$ cresc uploadApk app-release.apk --version "1.2.3-hotfix" +Using custom version: 1.2.3-hotfix +Successfully uploaded APK native package (id: 12345, version: 1.2.3-hotfix, buildTime: 2024-01-15T10:30:00Z) +``` diff --git a/src/locales/en.ts b/src/locales/en.ts index bafead7..68e8f57 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -130,4 +130,5 @@ This can reduce the risk of inconsistent dependencies and supply chain attacks. updateNativePackageQuestion: 'Bind to native package now?(Y/N)', unnamed: '(Unnamed)', dryRun: 'Below is the dry-run result, no actual operation will be performed:', + usingCustomVersion: 'Using custom version: {{version}}', }; diff --git a/src/locales/zh.ts b/src/locales/zh.ts index 8f8c4df..acb5652 100644 --- a/src/locales/zh.ts +++ b/src/locales/zh.ts @@ -123,4 +123,5 @@ export default { updateNativePackageQuestion: '是否现在将此热更应用到原生包上?(Y/N)', unnamed: '(未命名)', dryRun: '以下是 dry-run 模拟运行结果,不会实际执行任何操作:', + usingCustomVersion: '使用自定义版本:{{version}}', }; diff --git a/src/package.ts b/src/package.ts index 9fd5151..bc82fe7 100644 --- a/src/package.ts +++ b/src/package.ts @@ -23,9 +23,9 @@ export async function listPackage(appId: string) { let versionInfo = ''; if (version) { const versionObj = version as any; - versionInfo = t('boundTo', { - name: versionObj.name || version, - id: versionObj.id || version + versionInfo = t('boundTo', { + name: versionObj.name || version, + id: versionObj.id || version, }); } let output = pkg.name; @@ -57,16 +57,19 @@ export async function choosePackage(appId: string) { } export const packageCommands = { - uploadIpa: async ({ args }: { args: string[] }) => { + uploadIpa: async ({ + args, + options, + }: { + args: string[]; + options: Record; + }) => { const fn = args[0]; if (!fn || !fn.endsWith('.ipa')) { throw new Error(t('usageUploadIpa')); } const ipaInfo = await getIpaInfo(fn); - const { - versionName, - buildTime, - } = ipaInfo; + const { versionName: extractedVersionName, buildTime } = ipaInfo; const appIdInPkg = (ipaInfo as any).appId; const appKeyInPkg = (ipaInfo as any).appKey; const { appId, appKey } = await getSelectedApp('ios'); @@ -79,6 +82,12 @@ export const packageCommands = { throw new Error(t('appKeyMismatchIpa', { appKeyInPkg, appKey })); } + // Use custom version if provided, otherwise use extracted version + const versionName = options.version || extractedVersionName; + if (options.version) { + console.log(t('usingCustomVersion', { version: versionName })); + } + const { hash } = await uploadFile(fn); const { id } = await post(`/app/${appId}/package/create`, { @@ -89,20 +98,21 @@ export const packageCommands = { commit: await getCommitInfo(), }); saveToLocal(fn, `${appId}/package/${id}.ipa`); - console.log( - t('ipaUploadSuccess', { id, version: versionName, buildTime }), - ); + console.log(t('ipaUploadSuccess', { id, version: versionName, buildTime })); }, - uploadApk: async ({ args }: { args: string[] }) => { + uploadApk: async ({ + args, + options, + }: { + args: string[]; + options: Record; + }) => { const fn = args[0]; if (!fn || !fn.endsWith('.apk')) { throw new Error(t('usageUploadApk')); } const apkInfo = await getApkInfo(fn); - const { - versionName, - buildTime, - } = apkInfo; + const { versionName: extractedVersionName, buildTime } = apkInfo; const appIdInPkg = (apkInfo as any).appId; const appKeyInPkg = (apkInfo as any).appKey; const { appId, appKey } = await getSelectedApp('android'); @@ -115,6 +125,12 @@ export const packageCommands = { throw new Error(t('appKeyMismatchApk', { appKeyInPkg, appKey })); } + // Use custom version if provided, otherwise use extracted version + const versionName = options.version || extractedVersionName; + if (options.version) { + console.log(t('usingCustomVersion', { version: versionName })); + } + const { hash } = await uploadFile(fn); const { id } = await post(`/app/${appId}/package/create`, { @@ -125,20 +141,21 @@ export const packageCommands = { commit: await getCommitInfo(), }); saveToLocal(fn, `${appId}/package/${id}.apk`); - console.log( - t('apkUploadSuccess', { id, version: versionName, buildTime }), - ); + console.log(t('apkUploadSuccess', { id, version: versionName, buildTime })); }, - uploadApp: async ({ args }: { args: string[] }) => { + uploadApp: async ({ + args, + options, + }: { + args: string[]; + options: Record; + }) => { const fn = args[0]; if (!fn || !fn.endsWith('.app')) { throw new Error(t('usageUploadApp')); } const appInfo = await getAppInfo(fn); - const { - versionName, - buildTime, - } = appInfo; + const { versionName: extractedVersionName, buildTime } = appInfo; const appIdInPkg = (appInfo as any).appId; const appKeyInPkg = (appInfo as any).appKey; const { appId, appKey } = await getSelectedApp('harmony'); @@ -151,6 +168,12 @@ export const packageCommands = { throw new Error(t('appKeyMismatchApp', { appKeyInPkg, appKey })); } + // Use custom version if provided, otherwise use extracted version + const versionName = options.version || extractedVersionName; + if (options.version) { + console.log(t('usingCustomVersion', { version: versionName })); + } + const { hash } = await uploadFile(fn); const { id } = await post(`/app/${appId}/package/create`, { @@ -161,9 +184,7 @@ export const packageCommands = { commit: await getCommitInfo(), }); saveToLocal(fn, `${appId}/package/${id}.app`); - console.log( - t('appUploadSuccess', { id, version: versionName, buildTime }), - ); + console.log(t('appUploadSuccess', { id, version: versionName, buildTime })); }, parseApp: async ({ args }: { args: string[] }) => { const fn = args[0]; diff --git a/src/provider.ts b/src/provider.ts index e0dc70d..14b29c0 100644 --- a/src/provider.ts +++ b/src/provider.ts @@ -110,7 +110,7 @@ export class CLIProviderImpl implements CLIProvider { const context: CommandContext = { args: [filePath], - options: { platform, appId }, + options: { platform, appId, version: options.version }, }; const { packageCommands } = await import('./package'); diff --git a/src/types.ts b/src/types.ts index 909b4fa..339f1cb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -85,6 +85,7 @@ export interface UploadOptions { platform?: Platform; filePath: string; appId?: string; + version?: string; } export interface WorkflowStep {