Skip to content

Commit fbad14a

Browse files
committed
fix cli command dispatch
1 parent dfa19d3 commit fbad14a

7 files changed

Lines changed: 165 additions & 175 deletions

File tree

README.md

Lines changed: 2 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ A React Native Update command line tool for bundling, uploading native packages,
99
- Single `pushy` / `cresc` CLI entrypoint
1010
- Backward-compatible command set
1111
- Programmatic provider API for build scripts and CI/CD
12-
- Modular command registration for custom extensions
1312
- TypeScript type definitions
1413

1514
## Installation
@@ -32,9 +31,9 @@ npx pushy uploadIpa ./app.ipa
3231
## Programmatic Usage
3332

3433
```typescript
35-
import { moduleManager } from 'react-native-update-cli';
34+
import { CLIProviderImpl } from 'react-native-update-cli';
3635

37-
const provider = moduleManager.getProvider();
36+
const provider = new CLIProviderImpl();
3837

3938
const bundleResult = await provider.bundle({
4039
platform: 'ios',
@@ -53,56 +52,6 @@ const publishResult = await provider.publish({
5352
});
5453
```
5554

56-
## Custom Modules
57-
58-
```typescript
59-
import type {
60-
CLIModule,
61-
CommandContext,
62-
CommandResult,
63-
} from 'react-native-update-cli';
64-
65-
export const myCustomModule: CLIModule = {
66-
name: 'my-custom',
67-
version: '1.0.0',
68-
commands: [
69-
{
70-
name: 'custom-command',
71-
description: 'My custom command',
72-
handler: async (
73-
context: CommandContext,
74-
): Promise<CommandResult> => {
75-
return {
76-
success: true,
77-
data: { options: context.options },
78-
};
79-
},
80-
options: {
81-
param: { hasValue: true, description: 'Custom parameter' },
82-
},
83-
},
84-
],
85-
init: () => {
86-
console.log('Custom module initialized');
87-
},
88-
cleanup: () => {
89-
console.log('Custom module cleanup');
90-
},
91-
};
92-
```
93-
94-
```typescript
95-
import { moduleManager } from 'react-native-update-cli';
96-
import { myCustomModule } from './my-custom-module';
97-
98-
moduleManager.registerModule(myCustomModule);
99-
100-
const result = await moduleManager.executeCommand('custom-command', {
101-
args: [],
102-
options: { param: 'value' },
103-
});
104-
```
105-
10655
## Built-in Commands
10756

10857
### Bundle

README.zh-CN.md

Lines changed: 2 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ React Native Update 命令行工具,用于打包、上传原生包、发布 OT
99
- 统一的 `pushy` / `cresc` CLI 入口
1010
- 保持既有命令兼容
1111
- 可用于构建脚本和 CI/CD 的 Provider API
12-
- 支持通过模块注册自定义命令
1312
- 提供 TypeScript 类型定义
1413

1514
## 安装
@@ -32,9 +31,9 @@ npx pushy uploadIpa ./app.ipa
3231
## 编程调用
3332

3433
```typescript
35-
import { moduleManager } from 'react-native-update-cli';
34+
import { CLIProviderImpl } from 'react-native-update-cli';
3635

37-
const provider = moduleManager.getProvider();
36+
const provider = new CLIProviderImpl();
3837

3938
const bundleResult = await provider.bundle({
4039
platform: 'ios',
@@ -53,56 +52,6 @@ const publishResult = await provider.publish({
5352
});
5453
```
5554

56-
## 自定义模块
57-
58-
```typescript
59-
import type {
60-
CLIModule,
61-
CommandContext,
62-
CommandResult,
63-
} from 'react-native-update-cli';
64-
65-
export const myCustomModule: CLIModule = {
66-
name: 'my-custom',
67-
version: '1.0.0',
68-
commands: [
69-
{
70-
name: 'custom-command',
71-
description: 'My custom command',
72-
handler: async (
73-
context: CommandContext,
74-
): Promise<CommandResult> => {
75-
return {
76-
success: true,
77-
data: { options: context.options },
78-
};
79-
},
80-
options: {
81-
param: { hasValue: true, description: 'Custom parameter' },
82-
},
83-
},
84-
],
85-
init: () => {
86-
console.log('Custom module initialized');
87-
},
88-
cleanup: () => {
89-
console.log('Custom module cleanup');
90-
},
91-
};
92-
```
93-
94-
```typescript
95-
import { moduleManager } from 'react-native-update-cli';
96-
import { myCustomModule } from './my-custom-module';
97-
98-
moduleManager.registerModule(myCustomModule);
99-
100-
const result = await moduleManager.executeCommand('custom-command', {
101-
args: [],
102-
options: { param: 'value' },
103-
});
104-
```
105-
10655
## 内置命令
10756

10857
### Bundle

cli.json

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -331,13 +331,8 @@
331331
}
332332
},
333333
"list": {
334-
"description": "List all bundles",
335-
"options": {
336-
"output": {
337-
"default": "${tempDir}/output/list",
338-
"hasValue": true
339-
}
340-
}
334+
"description": "List all available commands",
335+
"options": {}
341336
},
342337
"install": {
343338
"description": "Install optional dependencies to the CLI",

src/index.ts

Lines changed: 15 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -5,48 +5,26 @@ import { appCommands } from './app';
55
import { bundleCommands } from './bundle';
66
import { diffCommands } from './diff';
77
import { installCommands } from './install';
8-
import { moduleManager } from './module-manager';
9-
import { builtinModules } from './modules';
108
import { packageCommands } from './package';
11-
import type { CommandContext } from './types';
129
import { userCommands } from './user';
1310
import { printVersionCommand } from './utils';
1411
import { t } from './utils/i18n';
1512
import { versionCommands } from './versions';
1613

17-
type LegacyCommandHandler = (argv: any) => Promise<unknown> | unknown;
14+
type CliCommandHandler = (argv: any) => Promise<unknown> | unknown;
1815

1916
interface CliArgv {
2017
command: string;
2118
args: string[];
2219
options: Record<string, any>;
2320
}
2421

25-
function registerBuiltinModules() {
26-
for (const module of builtinModules) {
27-
try {
28-
moduleManager.registerModule(module);
29-
} catch (error) {
30-
console.error(`Failed to register module ${module.name}:`, error);
31-
}
32-
}
33-
}
34-
35-
function printUsage() {
22+
function printUsage(exitCode = 1) {
3623
console.log('React Native Update CLI');
3724
console.log('');
38-
console.log('Traditional commands:');
39-
for (const name of Object.keys(legacyCommands)) {
40-
console.log(` ${name}: Legacy command`);
41-
}
42-
43-
console.log('');
44-
console.log('Modular commands:');
45-
const commands = moduleManager.listCommands();
46-
for (const command of commands) {
47-
console.log(
48-
` ${command.name}: ${command.description || 'No description'}`,
49-
);
25+
console.log('Commands:');
26+
for (const name of Object.keys(commandHandlers)) {
27+
console.log(` ${name}`);
5028
}
5129

5230
console.log('');
@@ -58,10 +36,10 @@ function printUsage() {
5836
console.log(
5937
'Visit `https://github.com/reactnativecn/react-native-update` for document.',
6038
);
61-
process.exit(1);
39+
process.exit(exitCode);
6240
}
6341

64-
const legacyCommands: Record<string, LegacyCommandHandler> = {
42+
const commandHandlers: Record<string, CliCommandHandler> = {
6543
...userCommands,
6644
...bundleCommands,
6745
...diffCommands,
@@ -78,41 +56,22 @@ async function run() {
7856
process.exit();
7957
}
8058

81-
// Register builtin modules for modular functionality
82-
registerBuiltinModules();
83-
8459
const argv: CliArgv = require('cli-arguments').parse(require('../cli.json'));
8560
global.NO_INTERACTIVE = argv.options['no-interactive'];
8661
global.USE_ACC_OSS = argv.options.acc;
8762

88-
const context: CommandContext = {
89-
args: argv.args || [],
90-
options: argv.options || {},
91-
};
92-
9363
try {
9464
await loadSession();
95-
context.session = require('./api').getSession();
9665

97-
// Handle special modular commands first
9866
if (argv.command === 'help') {
99-
printUsage();
67+
printUsage(0);
10068
} else if (argv.command === 'list') {
101-
moduleManager.listAll();
102-
}
103-
// Try legacy commands first for backward compatibility
104-
else if (legacyCommands[argv.command]) {
105-
const legacyHandler = legacyCommands[argv.command];
106-
await legacyHandler(argv);
107-
}
108-
// Fall back to modular commands
109-
else {
110-
const result = await moduleManager.executeCommand(argv.command, context);
111-
if (!result.success) {
112-
console.error('Command execution failed:', result.error);
113-
process.exit(1);
114-
}
115-
console.log('Command completed successfully:', result.data);
69+
printUsage(0);
70+
} else if (commandHandlers[argv.command]) {
71+
const handler = commandHandlers[argv.command];
72+
await handler(argv);
73+
} else {
74+
throw new Error(`Unknown command: ${argv.command}`);
11675
}
11776
} catch (err: any) {
11877
if (err.status === 401) {
@@ -124,12 +83,12 @@ async function run() {
12483
}
12584
}
12685

86+
export { moduleManager } from './module-manager';
12787
export { CLIProviderImpl } from './provider';
12888
export type {
12989
CLIModule,
13090
CLIProvider,
13191
CommandDefinition,
13292
} from './types';
133-
export { moduleManager };
13493

13594
run();

src/versions.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -403,13 +403,13 @@ export const bindVersionToPackages = async ({
403403
};
404404

405405
export const versionCommands = {
406-
publish: async function ({
406+
publish: async ({
407407
args,
408408
options,
409409
}: {
410410
args: string[];
411411
options: VersionCommandOptions;
412-
}) {
412+
}) => {
413413
const fn = args[0];
414414
const { name, description, metaInfo } = options;
415415

@@ -454,7 +454,7 @@ export const versionCommands = {
454454
minPackageVersion ||
455455
maxPackageVersion
456456
) {
457-
await this.update({
457+
await versionCommands.update({
458458
options: {
459459
versionId: id,
460460
platform,
@@ -472,7 +472,7 @@ export const versionCommands = {
472472
} else {
473473
const q = await question(t('updateNativePackageQuestion'));
474474
if (q.toLowerCase() === 'y') {
475-
await this.update({
475+
await versionCommands.update({
476476
options: {
477477
versionId: id,
478478
platform,

tests/command-handlers.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { describe, expect, test } from 'bun:test';
2+
import fs from 'fs';
3+
import path from 'path';
4+
import ts from 'typescript';
5+
6+
const commandFiles = [
7+
'src/app.ts',
8+
'src/bundle.ts',
9+
'src/diff.ts',
10+
'src/install.ts',
11+
'src/package.ts',
12+
'src/user.ts',
13+
'src/versions.ts',
14+
];
15+
16+
function findThisUsagesInCommandMaps(filePath: string): string[] {
17+
const sourceText = fs.readFileSync(filePath, 'utf8');
18+
const sourceFile = ts.createSourceFile(
19+
filePath,
20+
sourceText,
21+
ts.ScriptTarget.Latest,
22+
true,
23+
);
24+
const errors: string[] = [];
25+
26+
function visitCommandMap(node: ts.Node): void {
27+
if (!ts.isVariableDeclaration(node)) {
28+
ts.forEachChild(node, visitCommandMap);
29+
return;
30+
}
31+
32+
if (!ts.isIdentifier(node.name) || !node.name.text.endsWith('Commands')) {
33+
return;
34+
}
35+
36+
function scanForThis(child: ts.Node): void {
37+
if (child.kind === ts.SyntaxKind.ThisKeyword) {
38+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(
39+
child.getStart(sourceFile),
40+
);
41+
errors.push(`${filePath}:${line + 1}:${character + 1}`);
42+
}
43+
ts.forEachChild(child, scanForThis);
44+
}
45+
46+
if (node.initializer) {
47+
scanForThis(node.initializer);
48+
}
49+
}
50+
51+
visitCommandMap(sourceFile);
52+
return errors;
53+
}
54+
55+
describe('cli command maps', () => {
56+
test('do not depend on receiver this binding', () => {
57+
const projectRoot = path.resolve(import.meta.dir, '..');
58+
const thisUsages = commandFiles.flatMap((file) =>
59+
findThisUsagesInCommandMaps(path.join(projectRoot, file)),
60+
);
61+
62+
expect(thisUsages).toEqual([]);
63+
});
64+
});

0 commit comments

Comments
 (0)