Skip to content

Commit 850dfb7

Browse files
committed
Replaces explicit command registration w/ @command
1 parent 4a91d23 commit 850dfb7

File tree

3 files changed

+397
-446
lines changed

3 files changed

+397
-446
lines changed

src/env/node/gk/cli/commands.ts

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,56 +5,26 @@ import type { Container } from '../../../../container';
55
import { cherryPick, merge, rebase } from '../../../../git/actions/repository';
66
import type { Repository } from '../../../../git/models/repository';
77
import { executeCommand } from '../../../../system/-webview/command';
8+
import { createCommandDecorator } from '../../../../system/decorators/command';
89
import type { CliCommandRequest, CliCommandResponse, CliIpcServer } from './integration';
910

10-
interface CliCommand {
11-
command: string;
12-
handler: (request: CliCommandRequest, repo?: Repository | undefined) => Promise<CliCommandResponse>;
13-
}
14-
15-
const commandHandlers: CliCommand[] = [];
16-
function command(command: string) {
17-
return function (
18-
target: unknown,
19-
contextOrKey?: string | ClassMethodDecoratorContext,
20-
descriptor?: PropertyDescriptor,
21-
) {
22-
// ES Decorator
23-
if (contextOrKey && typeof contextOrKey === 'object' && 'kind' in contextOrKey) {
24-
if (contextOrKey.kind !== 'method') {
25-
throw new Error('The command decorator can only be applied to methods');
26-
}
11+
type CliCommandHandler = (request: CliCommandRequest, repo?: Repository | undefined) => Promise<CliCommandResponse>;
2712

28-
commandHandlers.push({ command: command, handler: target as CliCommand['handler'] });
29-
return;
30-
}
31-
32-
// TypeScript experimental decorator
33-
if (descriptor) {
34-
commandHandlers.push({ command: command, handler: descriptor.value as CliCommand['handler'] });
35-
return descriptor;
36-
}
37-
38-
throw new Error('Invalid decorator usage');
39-
};
40-
}
13+
const { command, getCommands } = createCommandDecorator<CliCommandHandler>();
4114

4215
export class CliCommandHandlers implements Disposable {
4316
constructor(
4417
private readonly container: Container,
4518
private readonly server: CliIpcServer,
4619
) {
47-
for (const { command, handler } of commandHandlers) {
20+
for (const { command, handler } of getCommands()) {
4821
this.server.registerHandler(command, rq => this.wrapHandler(rq, handler));
4922
}
5023
}
5124

5225
dispose(): void {}
5326

54-
private wrapHandler(
55-
request: CliCommandRequest,
56-
handler: (request: CliCommandRequest, repo?: Repository | undefined) => Promise<CliCommandResponse>,
57-
) {
27+
private wrapHandler(request: CliCommandRequest, handler: CliCommandHandler) {
5828
let repo: Repository | undefined;
5929
if (request?.cwd) {
6030
repo = this.container.git.getRepository(request.cwd);

src/system/decorators/command.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
interface Command<
2+
THandler extends (...args: any[]) => any,
3+
TCommand extends string = string,
4+
TOptions extends object | void = void,
5+
> {
6+
command: TCommand;
7+
handler: THandler;
8+
options?: TOptions;
9+
}
10+
11+
export function createCommandDecorator<
12+
THandler extends (...args: any[]) => any = (...args: any[]) => any,
13+
TCommand extends string = string,
14+
TOptions extends object | void = void,
15+
>(): {
16+
command: (
17+
command: TCommand,
18+
options?: TOptions,
19+
) => (
20+
target: unknown,
21+
contextOrKey?: string | ClassMethodDecoratorContext,
22+
descriptor?: PropertyDescriptor,
23+
) => PropertyDescriptor | undefined;
24+
getCommands: () => Iterable<Command<THandler, TCommand, TOptions>>;
25+
} {
26+
const commands = new Map<string, Command<THandler, TCommand, TOptions>>();
27+
28+
function command(command: TCommand, options?: TOptions) {
29+
return function (
30+
target: unknown,
31+
contextOrKey?: string | ClassMethodDecoratorContext,
32+
descriptor?: PropertyDescriptor,
33+
) {
34+
if (commands.has(command)) {
35+
debugger;
36+
throw new Error(`@command decorator has already been applied to the command: ${command}`);
37+
}
38+
39+
// ES Decorator
40+
if (contextOrKey && typeof contextOrKey === 'object' && 'kind' in contextOrKey) {
41+
if (contextOrKey.kind !== 'method') {
42+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
43+
throw new Error(`@command can only be used on methods, not on ${contextOrKey.kind}`);
44+
}
45+
46+
commands.set(command, { command: command, handler: target as THandler, options: options });
47+
return;
48+
}
49+
50+
// TypeScript experimental decorator
51+
if (descriptor) {
52+
if (typeof descriptor.value !== 'function') {
53+
throw new Error(`@command can only be used on methods, not on ${typeof descriptor.value}`);
54+
}
55+
56+
commands.set(command, { command: command, handler: descriptor.value as THandler, options: options });
57+
return descriptor;
58+
}
59+
60+
throw new Error('Invalid decorator usage');
61+
};
62+
}
63+
64+
return {
65+
command: command,
66+
getCommands: () => commands.values(),
67+
};
68+
}

0 commit comments

Comments
 (0)