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