Skip to content

Commit 14e94d8

Browse files
authored
fix: improve CLI messages and move them into separate file (#40)
1 parent dd5ad5c commit 14e94d8

File tree

11 files changed

+187
-63
lines changed

11 files changed

+187
-63
lines changed

src/components/Context/context.spec.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import React from 'react';
22
import { render } from 'ink-testing-library';
33
import { ListContexts, ShowCurrentContext, AddContext, SetCurrent } from './Context';
44
import { ContextTestingHelper } from '../../constants';
5+
import * as messages from '../../messages';
56

67
const testing = new ContextTestingHelper();
78

89
describe('listing contexts', () => {
910
test('should render error when no context file found', () => {
1011
testing.deleteDummyContextFile();
1112
const { lastFrame } = render(<ListContexts />);
12-
expect(lastFrame()).toMatch('No contexts saved yet.');
13+
expect(lastFrame()).toMatch(messages.NO_CONTEXTS_SAVED);
1314
});
1415

1516
test('Should render the context list', () => {
@@ -27,7 +28,7 @@ describe('rendering current context', () => {
2728
testing.deleteDummyContextFile();
2829
const { lastFrame } = render(<ShowCurrentContext />);
2930
const message = lastFrame();
30-
expect(message).toMatch('No contexts saved yet.');
31+
expect(message).toMatch(messages.NO_CONTEXTS_SAVED);
3132
});
3233

3334
test('showing current context ', () => {
@@ -41,15 +42,15 @@ describe('AddContext ', () => {
4142
test('should return message', () => {
4243
testing.createDummyContextFile();
4344
const { lastFrame } = render(<AddContext options={{}} args={['home', './test/specification.yml']} />);
44-
expect(lastFrame()).toMatch('New context added');
45+
expect(lastFrame()).toMatch(messages.NEW_CONTEXT_ADDED('home'));
4546
});
4647
});
4748

4849
describe('SetContext ', () => {
4950
test('Should render error message is key is not in store', () => {
5051
testing.createDummyContextFile();
5152
const { lastFrame } = render(<SetCurrent args={['name']} options={{}} />);
52-
expect(lastFrame()).toMatch('The context you are trying to use is not present');
53+
expect(lastFrame()).toMatch(messages.CONTEXT_NOT_FOUND('name'));
5354
});
5455

5556
test('Should render the update context', () => {

src/components/Context/contexterror.tsx

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,7 @@
11
import React from 'react';
22
import { Text } from 'ink';
33

4-
import { ContextFileNotFoundError, DeletingCurrentContextError, KeyNotFoundError } from '../../hooks/context';
5-
64
const ContextError: React.FunctionComponent<{ error: Error }> = ({ error }) => {
7-
if (error instanceof ContextFileNotFoundError) {
8-
return <Text>No contexts saved yet.</Text>;
9-
}
10-
11-
if (error instanceof KeyNotFoundError) {
12-
return <Text>The context you are trying to use is not present</Text>;
13-
}
14-
15-
if (error instanceof DeletingCurrentContextError) {
16-
return <Text>You are trying to delete a context that is set as current.</Text>;
17-
}
18-
195
return <Text>{error.message}</Text>;
206
};
217

src/help-message.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { HelpMessageBuilder } from './help-message';
2+
3+
let helpBuilder: HelpMessageBuilder;
4+
5+
describe('HelpMessageBuilder should', () => {
6+
beforeAll(() => {
7+
helpBuilder = new HelpMessageBuilder();
8+
});
9+
it('return root Help message', () => {
10+
expect(typeof helpBuilder.showHelp()).toMatch('string');
11+
expect(helpBuilder.showHelp()).toMatch(
12+
'usage: asyncapi [options] [command]\n\n'+
13+
'flags:\n'+
14+
' -h, --help display help for command\n'+
15+
' -v, --version output the version number\n'+
16+
'\n'+
17+
'commands:\n'+
18+
' validate [options] [command] Validate asyncapi file\n'+
19+
' context [options] [command] Manage context\n'
20+
);
21+
});
22+
23+
it('return validate help message', () => {
24+
expect(typeof helpBuilder.showCommandHelp('validate')).toMatch('string');
25+
});
26+
27+
it('return context help message', () => {
28+
expect(typeof helpBuilder.showCommandHelp('context')).toMatch('string');
29+
});
30+
});

src/help-message.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { injectable, container } from 'tsyringe';
2+
3+
export type CommandName = 'validate' | 'context';
4+
5+
export type Command = {
6+
[name in CommandName]: {
7+
usage: string;
8+
shortDescription: string;
9+
longDescription?: string;
10+
flags: string[];
11+
subCommands?: string[];
12+
};
13+
};
14+
15+
@injectable()
16+
export class HelpMessage {
17+
private helpFlag = '-h, --help display help for command';
18+
19+
readonly usage: string = 'asyncapi [options] [command]';
20+
21+
readonly flags = [
22+
this.helpFlag,
23+
'-v, --version output the version number',
24+
];
25+
26+
readonly commands: Command = {
27+
validate: {
28+
usage: 'asyncapi validate [options]',
29+
shortDescription: 'Validate asyncapi file',
30+
flags: [
31+
this.helpFlag,
32+
'-f, --file <spec-file-path> Path of the AsyncAPI file',
33+
'-c, --context <saved-context-name> Context to use',
34+
'-w, --watch Watch mode'
35+
]
36+
},
37+
context: {
38+
usage: 'asyncapi context [command] [options]',
39+
shortDescription: 'Manage context',
40+
longDescription: 'Context is what makes it easier for you to work with multiple AsyncAPI files.\nYou can add multiple different files to a context.\nThis way you do not have to pass --file flag with path to the file every time but just --context flag with reference name.\nYou can also set a default context, so neither --file nor --context flags are needed',
41+
flags: [this.helpFlag],
42+
subCommands: [
43+
'list list all saved contexts',
44+
'current see current context',
45+
'use <context-name> set given context as default/current',
46+
'add <context-name> <spec-file-path> add/update context',
47+
'remove <context-name> remove a context'
48+
]
49+
}
50+
}
51+
}
52+
53+
export class HelpMessageBuilder {
54+
private helpMessage: HelpMessage = container.resolve(HelpMessage);
55+
56+
showHelp() {
57+
let helpText = '';
58+
helpText += `usage: ${this.helpMessage.usage}\n\n`;
59+
helpText += 'flags:\n';
60+
for (const flag of this.helpMessage.flags) {
61+
helpText += ` ${flag}\n`;
62+
}
63+
helpText += '\n';
64+
65+
if (this.helpMessage.commands) {
66+
helpText += 'commands:\n';
67+
for (const [name, obj] of Object.entries(this.helpMessage.commands)) {
68+
helpText += ` ${name} [options] [command] ${obj.shortDescription}\n`;
69+
}
70+
}
71+
72+
return helpText;
73+
}
74+
75+
showCommandHelp(command: CommandName) {
76+
let helpText = '';
77+
const commandHelpObject = this.helpMessage.commands[command as CommandName];
78+
helpText += `usage: ${commandHelpObject.usage}\n\n`;
79+
80+
if (commandHelpObject.longDescription) {
81+
helpText += `${commandHelpObject.longDescription}\n\n`;
82+
}
83+
84+
helpText += 'flags: \n';
85+
for (const flag of commandHelpObject.flags) {
86+
helpText += ` ${flag}\n`;
87+
}
88+
89+
if (commandHelpObject.subCommands) {
90+
helpText += '\n';
91+
helpText += 'commands:\n';
92+
for (const command of commandHelpObject.subCommands) {
93+
helpText += ` ${command}\n`;
94+
}
95+
}
96+
97+
return helpText;
98+
}
99+
}

src/hooks/context/contextService.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { injectable } from 'tsyringe';
2-
import { Context, ContextFileNotFoundError,KeyNotFoundError, SpecFileNotFoundError } from './models';
2+
import { Context, ContextFileNotFoundError, ContextNotFoundError, SpecFileNotFoundError } from './models';
33
import { CONTEXTFILE_PATH } from '../../constants';
44
import * as fs from 'fs';
55
import * as path from 'path';
@@ -26,7 +26,7 @@ export class ContextService {
2626
}
2727

2828
addContext(context: Context, key: string, specFile: SpecificationFile): Context {
29-
if (specFile.isNotValid()) {throw new SpecFileNotFoundError();}
29+
if (specFile.isNotValid()) {throw new SpecFileNotFoundError(specFile.getSpecificationName());}
3030
context.store[String(key)] = specFile.getSpecificationName();
3131
return context;
3232
}
@@ -38,7 +38,7 @@ export class ContextService {
3838
}
3939

4040
updateCurrent(context: Context, key: string): Context {
41-
if (!context.store[String(key)]) {throw new KeyNotFoundError();}
41+
if (!context.store[String(key)]) {throw new ContextNotFoundError(key);}
4242
context.current = key;
4343
return context;
4444
}

src/hooks/context/hook.spec.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { useContextFile, useSpecfile } from './hooks';
2-
import { ContextFileNotFoundError, KeyNotFoundError, ContextNotFoundError } from './models';
2+
import { ContextFileNotFoundError, ContextNotFoundError } from './models';
33
import { ContextTestingHelper } from '../../constants';
44
import { SpecificationFile } from '../validation';
5+
import * as messages from '../../messages';
56

67
const testingVariables = new ContextTestingHelper();
78

@@ -42,22 +43,22 @@ describe('useContextFile().addContext ', () => {
4243
testingVariables.deleteDummyContextFile();
4344
const { response, error } = useContextFile().addContext('home', new SpecificationFile('./test/specification.yml'));
4445
expect(error).toBeUndefined();
45-
expect(response).toMatch('New context added');
46+
expect(response).toMatch(messages.NEW_CONTEXT_ADDED('home'));
4647
testingVariables.deleteDummyContextFile();
4748
});
4849

4950
test('should save when context file is present', () => {
5051
testingVariables.createDummyContextFile();
5152
const { response, error } = useContextFile().addContext('home', new SpecificationFile('./test/specification.yml'));
5253
expect(error).toBeUndefined();
53-
expect(response).toMatch('New context added');
54+
expect(response).toMatch(messages.NEW_CONTEXT_ADDED('home'));
5455
});
5556

5657
test('Auto set current when when adding context for the fist time', () => {
5758
testingVariables.deleteDummyContextFile();
5859
const { response, error } = useContextFile().addContext('home', new SpecificationFile('./test/specification.yml'));
5960
expect(error).toBeUndefined();
60-
expect(response).toMatch('New context added');
61+
expect(response).toMatch(messages.NEW_CONTEXT_ADDED('home'));
6162
const { response: res, error: err } = useContextFile().current();
6263
expect(err).toBeUndefined();
6364
expect(res?.key).toMatch('home');
@@ -77,7 +78,7 @@ describe('useContextFile.updateCurrent ', () => {
7778
testingVariables.createDummyContextFile();
7879
const { response, error } = useContextFile().setCurrent('name');
7980
expect(response).toBeUndefined();
80-
expect(error instanceof KeyNotFoundError).toBeTruthy();
81+
expect(error instanceof ContextNotFoundError).toBeTruthy();
8182
});
8283

8384
test('Should update the current context', () => {
@@ -93,13 +94,13 @@ describe('useContextFile().deleteContext ', () => {
9394
testingVariables.createDummyContextFile();
9495
const { response, error } = useContextFile().deleteContext('code');
9596
expect(error).toBeUndefined();
96-
expect(response).toMatch('context deleted successfully');
97+
expect(response).toMatch(messages.CONTEXT_DELETED);
9798
});
9899

99100
test('return error if deleting current context', () => {
100101
testingVariables.createDummyContextFile();
101102
const { response, error } = useContextFile().deleteContext('home');
102-
expect(response).toMatch('context deleted successfully');
103+
expect(response).toMatch(messages.CONTEXT_DELETED);
103104
expect(error).toBeUndefined();
104105
});
105106
});

src/hooks/context/hooks.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Context, ContextFileNotFoundError, ContextNotFoundError, MissingCurrent
22
import { ContextService } from './contextService';
33
import { container } from 'tsyringe';
44
import { SpecificationFile } from '../validation';
5+
import * as messages from '../../messages';
56

67
export type Result = {
78
response?: any,
@@ -36,15 +37,15 @@ export const useContextFile = (): any => {
3637
const ctx = contextService.loadContextFile();
3738
const updatedContext = contextService.addContext(ctx, key, specFile);
3839
contextService.save(updatedContext);
39-
const response = 'New context added';
40+
const response = messages.NEW_CONTEXT_ADDED(key);
4041
return { response };
4142
} catch (error) {
4243
if (error instanceof ContextFileNotFoundError) {
4344
const context: Context = { current: '', store: {} };
4445
try {
4546
const newContext = contextService.addContext(context, key, specFile);
4647
contextService.save(contextService.updateCurrent(newContext, key));
47-
const response = 'New context added';
48+
const response = messages.NEW_CONTEXT_ADDED(key);
4849
return { response };
4950
} catch (error) {
5051
return { error };
@@ -69,11 +70,11 @@ export const useContextFile = (): any => {
6970
const ctx = contextService.loadContextFile();
7071
if (Object.keys(ctx.store).length === 1) {
7172
contextService.deleteContextFile();
72-
return { response: 'context deleted successfully' };
73+
return { response: messages.CONTEXT_DELETED };
7374
}
7475
const updatedContext = contextService.deleteContext(ctx, key);
7576
contextService.save(updatedContext);
76-
const response = 'context deleted successfully';
77+
const response = messages.CONTEXT_DELETED;
7778
return { response };
7879
} catch (error) {
7980
return { error };
@@ -102,7 +103,7 @@ export const useContextFile = (): any => {
102103
try {
103104
const ctx = contextService.loadContextFile();
104105
const ctxValue = ctx.store[String(key)];
105-
if (!ctxValue) { throw new ContextNotFoundError(); }
106+
if (!ctxValue) { throw new ContextNotFoundError(key); }
106107
const response = new SpecificationFile(ctxValue);
107108
return { response };
108109
} catch (error) {
@@ -136,7 +137,7 @@ export const useSpecfile = (flags: useSpecFileInput): useSpecFileOutput => {
136137

137138
if (flags.context) {
138139
const ctxFile = ctx.store[flags.context];
139-
if (!ctxFile) { throw new ContextNotFoundError(); }
140+
if (!ctxFile) { throw new ContextNotFoundError(flags.context); }
140141
const specFile = new SpecificationFile(ctxFile);
141142
return { specFile };
142143
}

0 commit comments

Comments
 (0)