@poetora/cli 0.0.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -0
- package/LICENSE +93 -0
- package/bin/accessibility.js +2 -2
- package/bin/cli-builder.d.ts +8 -0
- package/bin/cli-builder.js +178 -0
- package/bin/cli.d.ts +5 -11
- package/bin/cli.js +8 -200
- package/bin/commands/base.command.d.ts +13 -0
- package/bin/commands/base.command.js +40 -0
- package/bin/commands/check.command.d.ts +14 -0
- package/bin/commands/check.command.js +21 -0
- package/bin/commands/dev.command.d.ts +13 -0
- package/bin/commands/dev.command.js +40 -0
- package/bin/commands/index.d.ts +6 -0
- package/bin/commands/index.js +6 -0
- package/bin/commands/init.command.d.ts +16 -0
- package/bin/commands/init.command.js +88 -0
- package/bin/commands/link.command.d.ts +13 -0
- package/bin/commands/link.command.js +19 -0
- package/bin/commands/update.command.d.ts +10 -0
- package/bin/commands/update.command.js +13 -0
- package/bin/errors/cli-error.d.ts +26 -0
- package/bin/errors/cli-error.js +53 -0
- package/bin/errors/index.d.ts +1 -0
- package/bin/errors/index.js +1 -0
- package/bin/index.js +3 -3
- package/bin/mdxAccessibility.js +2 -2
- package/bin/services/accessibility-check.service.d.ts +10 -0
- package/bin/services/accessibility-check.service.js +144 -0
- package/bin/services/index.d.ts +7 -0
- package/bin/services/index.js +7 -0
- package/bin/services/link.service.d.ts +7 -0
- package/bin/services/link.service.js +40 -0
- package/bin/services/openapi-check.service.d.ts +7 -0
- package/bin/services/openapi-check.service.js +43 -0
- package/bin/services/port.service.d.ts +7 -0
- package/bin/services/port.service.js +26 -0
- package/bin/services/template.service.d.ts +22 -0
- package/bin/services/template.service.js +127 -0
- package/bin/services/update.service.d.ts +10 -0
- package/bin/services/update.service.js +57 -0
- package/bin/services/version.service.d.ts +16 -0
- package/bin/services/version.service.js +102 -0
- package/bin/types/common.d.ts +38 -0
- package/bin/types/common.js +21 -0
- package/bin/types/index.d.ts +2 -0
- package/bin/types/index.js +2 -0
- package/bin/types/options.d.ts +23 -0
- package/bin/types/options.js +1 -0
- package/bin/utils/console-logger.d.ts +16 -0
- package/bin/utils/console-logger.js +65 -0
- package/bin/utils/index.d.ts +2 -0
- package/bin/utils/index.js +2 -0
- package/bin/utils/logger.interface.d.ts +15 -0
- package/bin/utils/logger.interface.js +1 -0
- package/package.json +30 -31
- package/src/accessibility.ts +2 -2
- package/src/cli-builder.ts +267 -0
- package/src/cli.ts +15 -0
- package/src/commands/__tests__/base.command.test.ts +145 -0
- package/src/commands/__tests__/dev.command.test.ts +241 -0
- package/src/commands/__tests__/init.command.test.ts +281 -0
- package/{__test__ → src/commands/__tests__}/utils.ts +1 -1
- package/src/commands/base.command.ts +97 -0
- package/src/commands/check.command.ts +40 -0
- package/src/commands/dev.command.ts +63 -0
- package/src/commands/index.ts +6 -0
- package/src/commands/init.command.ts +125 -0
- package/src/commands/link.command.ts +39 -0
- package/src/commands/update.command.ts +23 -0
- package/src/errors/cli-error.ts +83 -0
- package/src/errors/index.ts +1 -0
- package/src/index.ts +4 -4
- package/src/mdxAccessibility.ts +3 -4
- package/src/services/__tests__/port.service.test.ts +83 -0
- package/src/services/__tests__/template.service.test.ts +234 -0
- package/src/services/__tests__/version.service.test.ts +165 -0
- package/src/services/accessibility-check.service.ts +226 -0
- package/src/services/index.ts +7 -0
- package/src/services/link.service.ts +65 -0
- package/src/services/openapi-check.service.ts +68 -0
- package/src/services/port.service.ts +47 -0
- package/src/services/template.service.ts +203 -0
- package/src/services/update.service.ts +76 -0
- package/src/services/version.service.ts +161 -0
- package/src/types/common.ts +53 -0
- package/src/types/index.ts +2 -0
- package/src/types/options.ts +42 -0
- package/src/utils/console-logger.ts +114 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/logger.interface.ts +70 -0
- package/tsconfig.build.json +2 -1
- package/tsconfig.json +1 -1
- package/.prettierignore +0 -2
- package/__test__/brokenLinks.test.ts +0 -93
- package/__test__/checkPort.test.ts +0 -92
- package/__test__/openApiCheck.test.ts +0 -127
- package/__test__/update.test.ts +0 -108
- package/bin/accessibilityCheck.d.ts +0 -2
- package/bin/accessibilityCheck.js +0 -70
- package/bin/helpers.d.ts +0 -17
- package/bin/helpers.js +0 -104
- package/bin/init.d.ts +0 -1
- package/bin/init.js +0 -73
- package/bin/mdxLinter.d.ts +0 -2
- package/bin/mdxLinter.js +0 -45
- package/bin/update.d.ts +0 -3
- package/bin/update.js +0 -32
- package/src/accessibilityCheck.tsx +0 -145
- package/src/cli.tsx +0 -302
- package/src/helpers.tsx +0 -131
- package/src/init.tsx +0 -93
- package/src/mdxLinter.tsx +0 -88
- package/src/update.tsx +0 -37
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { input, select } from '@inquirer/prompts';
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { ValidationError } from '../../errors/index.js';
|
|
4
|
+
import type { TemplateService } from '../../services/template.service.js';
|
|
5
|
+
import type { InitOptions } from '../../types/index.js';
|
|
6
|
+
import type { ILogger } from '../../utils/index.js';
|
|
7
|
+
import { InitCommand } from '../init.command.js';
|
|
8
|
+
|
|
9
|
+
vi.mock('@inquirer/prompts');
|
|
10
|
+
|
|
11
|
+
describe('InitCommand', () => {
|
|
12
|
+
let command: InitCommand;
|
|
13
|
+
let mockLogger: ILogger;
|
|
14
|
+
let mockTemplateService: TemplateService;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
mockLogger = {
|
|
18
|
+
info: vi.fn(),
|
|
19
|
+
success: vi.fn(),
|
|
20
|
+
error: vi.fn(),
|
|
21
|
+
warn: vi.fn(),
|
|
22
|
+
spinner: vi.fn().mockReturnValue({
|
|
23
|
+
start: vi.fn(),
|
|
24
|
+
succeed: vi.fn(),
|
|
25
|
+
fail: vi.fn(),
|
|
26
|
+
stop: vi.fn(),
|
|
27
|
+
}),
|
|
28
|
+
log: vi.fn(),
|
|
29
|
+
logColor: vi.fn(),
|
|
30
|
+
logBold: vi.fn(),
|
|
31
|
+
logSeparator: vi.fn(),
|
|
32
|
+
logNewLine: vi.fn(),
|
|
33
|
+
logHeader: vi.fn(),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
mockTemplateService = {
|
|
37
|
+
checkDirectory: vi.fn(),
|
|
38
|
+
getAvailableThemes: vi.fn(),
|
|
39
|
+
installTemplate: vi.fn(),
|
|
40
|
+
} as unknown as TemplateService;
|
|
41
|
+
|
|
42
|
+
command = new InitCommand(mockLogger, mockTemplateService);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
vi.clearAllMocks();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('run - empty directory', () => {
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
vi.mocked(mockTemplateService.checkDirectory).mockResolvedValue({
|
|
52
|
+
exists: true,
|
|
53
|
+
hasContents: false,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
vi.mocked(mockTemplateService.getAvailableThemes).mockReturnValue(['ora', 'sol']);
|
|
57
|
+
|
|
58
|
+
vi.mocked(input).mockResolvedValue('My Project');
|
|
59
|
+
vi.mocked(select).mockResolvedValue('ora');
|
|
60
|
+
vi.mocked(mockTemplateService.installTemplate).mockResolvedValue(undefined);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should install template directly for empty directory', async () => {
|
|
64
|
+
const options: InitOptions = {
|
|
65
|
+
directory: '.',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
await command.run(options);
|
|
69
|
+
|
|
70
|
+
expect(mockTemplateService.checkDirectory).toHaveBeenCalledWith('.');
|
|
71
|
+
expect(input).toHaveBeenCalled();
|
|
72
|
+
expect(select).toHaveBeenCalled();
|
|
73
|
+
expect(mockTemplateService.installTemplate).toHaveBeenCalledWith({
|
|
74
|
+
directory: '.',
|
|
75
|
+
projectName: 'My Project',
|
|
76
|
+
theme: 'ora',
|
|
77
|
+
});
|
|
78
|
+
expect(mockLogger.success).toHaveBeenCalledWith('Documentation Setup!');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should use directory name as default project name', async () => {
|
|
82
|
+
vi.mocked(input).mockResolvedValue('my-docs');
|
|
83
|
+
|
|
84
|
+
const options: InitOptions = {
|
|
85
|
+
directory: 'my-docs',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
await command.run(options);
|
|
89
|
+
|
|
90
|
+
expect(input).toHaveBeenCalledWith({
|
|
91
|
+
message: 'Project Name',
|
|
92
|
+
default: 'my-docs',
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should use "Poetora" as default for current directory', async () => {
|
|
97
|
+
vi.mocked(input).mockResolvedValue('Poetora');
|
|
98
|
+
|
|
99
|
+
const options: InitOptions = {
|
|
100
|
+
directory: '.',
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
await command.run(options);
|
|
104
|
+
|
|
105
|
+
expect(input).toHaveBeenCalledWith({
|
|
106
|
+
message: 'Project Name',
|
|
107
|
+
default: 'Poetora',
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('run - non-empty directory', () => {
|
|
113
|
+
beforeEach(() => {
|
|
114
|
+
vi.mocked(mockTemplateService.checkDirectory).mockResolvedValue({
|
|
115
|
+
exists: true,
|
|
116
|
+
hasContents: true,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
vi.mocked(mockTemplateService.getAvailableThemes).mockReturnValue(['quartz']);
|
|
120
|
+
vi.mocked(input).mockResolvedValue('My Project');
|
|
121
|
+
vi.mocked(mockTemplateService.installTemplate).mockResolvedValue(undefined);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should cancel installation when user chooses cancel', async () => {
|
|
125
|
+
vi.mocked(select).mockResolvedValueOnce('cancel');
|
|
126
|
+
|
|
127
|
+
const options: InitOptions = {
|
|
128
|
+
directory: 'existing-dir',
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
await command.run(options);
|
|
132
|
+
|
|
133
|
+
expect(mockLogger.info).toHaveBeenCalledWith('Installation cancelled');
|
|
134
|
+
expect(mockTemplateService.installTemplate).not.toHaveBeenCalled();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should overwrite when user chooses overwrite', async () => {
|
|
138
|
+
vi.mocked(select)
|
|
139
|
+
.mockResolvedValueOnce('overwrite') // directory choice
|
|
140
|
+
.mockResolvedValueOnce('ora'); // theme choice
|
|
141
|
+
|
|
142
|
+
const options: InitOptions = {
|
|
143
|
+
directory: 'existing-dir',
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
await command.run(options);
|
|
147
|
+
|
|
148
|
+
expect(mockTemplateService.installTemplate).toHaveBeenCalledWith({
|
|
149
|
+
directory: 'existing-dir',
|
|
150
|
+
projectName: 'My Project',
|
|
151
|
+
theme: 'ora',
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should create subdirectory when user chooses subdir', async () => {
|
|
156
|
+
vi.mocked(select)
|
|
157
|
+
.mockResolvedValueOnce('subdir') // directory choice
|
|
158
|
+
.mockResolvedValueOnce('ora'); // theme choice
|
|
159
|
+
|
|
160
|
+
vi.mocked(input)
|
|
161
|
+
.mockResolvedValueOnce('docs') // subdirectory name
|
|
162
|
+
.mockResolvedValueOnce('My Project'); // project name
|
|
163
|
+
|
|
164
|
+
const options: InitOptions = {
|
|
165
|
+
directory: '.',
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
await command.run(options);
|
|
169
|
+
|
|
170
|
+
expect(input).toHaveBeenCalledWith({
|
|
171
|
+
message: 'Subdirectory name:',
|
|
172
|
+
default: 'docs',
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
expect(mockTemplateService.installTemplate).toHaveBeenCalledWith({
|
|
176
|
+
directory: 'docs',
|
|
177
|
+
projectName: 'My Project',
|
|
178
|
+
theme: 'ora',
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should combine parent dir with subdir when not current directory', async () => {
|
|
183
|
+
vi.mocked(select).mockResolvedValueOnce('subdir').mockResolvedValueOnce('ora');
|
|
184
|
+
|
|
185
|
+
vi.mocked(input).mockResolvedValueOnce('docs').mockResolvedValueOnce('My Project');
|
|
186
|
+
|
|
187
|
+
const options: InitOptions = {
|
|
188
|
+
directory: 'parent',
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
await command.run(options);
|
|
192
|
+
|
|
193
|
+
expect(mockTemplateService.installTemplate).toHaveBeenCalledWith({
|
|
194
|
+
directory: 'parent/docs',
|
|
195
|
+
projectName: 'My Project',
|
|
196
|
+
theme: 'ora',
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should throw ValidationError for empty subdirectory name', async () => {
|
|
201
|
+
vi.mocked(select).mockResolvedValue('subdir');
|
|
202
|
+
vi.mocked(input).mockResolvedValue(' '); // empty with spaces
|
|
203
|
+
|
|
204
|
+
const options: InitOptions = {
|
|
205
|
+
directory: '.',
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
await expect(command.run(options)).rejects.toThrow(ValidationError);
|
|
209
|
+
await expect(command.run(options)).rejects.toThrow('Subdirectory name cannot be empty');
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe('theme selection', () => {
|
|
214
|
+
beforeEach(() => {
|
|
215
|
+
vi.mocked(mockTemplateService.checkDirectory).mockResolvedValue({
|
|
216
|
+
exists: true,
|
|
217
|
+
hasContents: false,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
vi.mocked(input).mockResolvedValue('My Project');
|
|
221
|
+
vi.mocked(mockTemplateService.installTemplate).mockResolvedValue(undefined);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should display all available themes', async () => {
|
|
225
|
+
vi.mocked(mockTemplateService.getAvailableThemes).mockReturnValue(['ora', 'sol', 'custom']);
|
|
226
|
+
|
|
227
|
+
vi.mocked(select).mockResolvedValue('sol');
|
|
228
|
+
|
|
229
|
+
const options: InitOptions = {
|
|
230
|
+
directory: '.',
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
await command.run(options);
|
|
234
|
+
|
|
235
|
+
expect(select).toHaveBeenCalledWith({
|
|
236
|
+
message: 'Theme',
|
|
237
|
+
choices: [
|
|
238
|
+
{ name: 'ora', value: 'ora' },
|
|
239
|
+
{ name: 'sol', value: 'sol' },
|
|
240
|
+
{ name: 'custom', value: 'custom' },
|
|
241
|
+
],
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe('onboarding message', () => {
|
|
247
|
+
beforeEach(() => {
|
|
248
|
+
vi.mocked(mockTemplateService.checkDirectory).mockResolvedValue({
|
|
249
|
+
exists: true,
|
|
250
|
+
hasContents: false,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
vi.mocked(mockTemplateService.getAvailableThemes).mockReturnValue(['ora']);
|
|
254
|
+
vi.mocked(input).mockResolvedValue('My Project');
|
|
255
|
+
vi.mocked(select).mockResolvedValue('ora');
|
|
256
|
+
vi.mocked(mockTemplateService.installTemplate).mockResolvedValue(undefined);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should show cd command for subdirectory installation', async () => {
|
|
260
|
+
const options: InitOptions = {
|
|
261
|
+
directory: 'docs',
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
await command.run(options);
|
|
265
|
+
|
|
266
|
+
expect(mockLogger.log).toHaveBeenCalledWith(' cd docs');
|
|
267
|
+
expect(mockLogger.log).toHaveBeenCalledWith(' poet dev');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should not show cd command for current directory', async () => {
|
|
271
|
+
const options: InitOptions = {
|
|
272
|
+
directory: '.',
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
await command.run(options);
|
|
276
|
+
|
|
277
|
+
expect(mockLogger.log).not.toHaveBeenCalledWith(expect.stringContaining('cd'));
|
|
278
|
+
expect(mockLogger.log).toHaveBeenCalledWith(' poet dev');
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { CliError } from '../errors/index.js';
|
|
2
|
+
import type { ILogger } from '../utils/index.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Base abstract class for all CLI commands
|
|
6
|
+
* Implements Template Method pattern for consistent command execution flow
|
|
7
|
+
*/
|
|
8
|
+
export abstract class BaseCommand<TOptions = unknown, TResult = void> {
|
|
9
|
+
/**
|
|
10
|
+
* Command name (e.g., 'dev', 'init', 'check')
|
|
11
|
+
*/
|
|
12
|
+
abstract readonly name: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Command description for help text
|
|
16
|
+
*/
|
|
17
|
+
abstract readonly description: string;
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
protected readonly logger: ILogger,
|
|
21
|
+
protected readonly packageName: string = 'poet'
|
|
22
|
+
) {}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Template method that orchestrates command execution
|
|
26
|
+
* This is the main entry point called by the CLI framework
|
|
27
|
+
*/
|
|
28
|
+
/* prettier-ignore */
|
|
29
|
+
async run(options: TOptions): Promise<TResult> {
|
|
30
|
+
try {
|
|
31
|
+
// 1. Validate input options
|
|
32
|
+
await this.validate(options);
|
|
33
|
+
|
|
34
|
+
// 2. Execute command logic
|
|
35
|
+
const result = await this.execute(options);
|
|
36
|
+
|
|
37
|
+
// 3. Return result
|
|
38
|
+
return result;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
// 4. Handle errors consistently
|
|
41
|
+
this.handleError(error);
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Validate command options before execution
|
|
48
|
+
* Override this method to add custom validation logic
|
|
49
|
+
* @param options - Command options to validate
|
|
50
|
+
* @throws {ValidationError} if validation fails
|
|
51
|
+
*/
|
|
52
|
+
// prettier-ignore
|
|
53
|
+
protected async validate(_options: TOptions): Promise<void> {
|
|
54
|
+
// Default: no validation
|
|
55
|
+
// Subclasses can override to add validation
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Execute the main command logic
|
|
60
|
+
* This method must be implemented by all command subclasses
|
|
61
|
+
* @param options - Validated command options
|
|
62
|
+
* @returns Command execution result
|
|
63
|
+
*/
|
|
64
|
+
protected abstract execute(options: TOptions): Promise<TResult>;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Handle errors that occur during command execution
|
|
68
|
+
* Can be overridden to provide custom error handling
|
|
69
|
+
* @param error - The error to handle
|
|
70
|
+
*/
|
|
71
|
+
protected handleError(error: unknown): void {
|
|
72
|
+
if (error instanceof CliError) {
|
|
73
|
+
// CLI-specific errors with user-friendly messages
|
|
74
|
+
this.logger.error(error.message);
|
|
75
|
+
} else if (error instanceof Error) {
|
|
76
|
+
// Generic errors
|
|
77
|
+
this.logger.error(error.message);
|
|
78
|
+
|
|
79
|
+
// In debug mode, show stack trace
|
|
80
|
+
if (process.env.DEBUG === 'true') {
|
|
81
|
+
console.error(error.stack);
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
// Unknown error types
|
|
85
|
+
this.logger.error('An unexpected error occurred');
|
|
86
|
+
console.error(error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Exit the process with given code
|
|
92
|
+
* Separated for easier testing (can be mocked)
|
|
93
|
+
*/
|
|
94
|
+
protected exit(code: number): never {
|
|
95
|
+
process.exit(code);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { AccessibilityCheckService, OpenApiCheckService } from '../services/index.js';
|
|
2
|
+
import type { OpenApiCheckOptions } from '../types/index.js';
|
|
3
|
+
import type { ILogger } from '../utils/index.js';
|
|
4
|
+
import { BaseCommand } from './base.command.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Check command - Validate OpenAPI specs and accessibility
|
|
8
|
+
*/
|
|
9
|
+
export class CheckCommand extends BaseCommand {
|
|
10
|
+
readonly name = 'check';
|
|
11
|
+
readonly description = 'check OpenAPI specs or accessibility';
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
logger: ILogger,
|
|
15
|
+
private readonly openApiCheckService: OpenApiCheckService,
|
|
16
|
+
private readonly accessibilityCheckService: AccessibilityCheckService,
|
|
17
|
+
packageName: string = 'poet'
|
|
18
|
+
) {
|
|
19
|
+
super(logger, packageName);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Execute OpenAPI check
|
|
24
|
+
*/
|
|
25
|
+
async checkOpenApi(options: OpenApiCheckOptions): Promise<void> {
|
|
26
|
+
await this.openApiCheckService.validateSpec(options.filename, options.localSchema);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Execute accessibility check
|
|
31
|
+
*/
|
|
32
|
+
async checkAccessibility(): Promise<number> {
|
|
33
|
+
return await this.accessibilityCheckService.checkAccessibility();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
protected override async execute(options: OpenApiCheckOptions): Promise<void> {
|
|
37
|
+
// This is for openapi-check command
|
|
38
|
+
await this.openApiCheckService.validateSpec(options.filename, options.localSchema);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { dev } from '@poetora/previewing';
|
|
2
|
+
import type { ArgumentsCamelCase } from 'yargs';
|
|
3
|
+
import { InvalidEnvironmentError } from '../errors/index.js';
|
|
4
|
+
import type { PortService, VersionService } from '../services/index.js';
|
|
5
|
+
import type { DevOptions } from '../types/index.js';
|
|
6
|
+
import type { ILogger } from '../utils/index.js';
|
|
7
|
+
import { BaseCommand } from './base.command.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Dev command - Start local development server
|
|
11
|
+
*/
|
|
12
|
+
export class DevCommand extends BaseCommand {
|
|
13
|
+
readonly name = 'dev';
|
|
14
|
+
readonly description = 'initialize a local preview environment';
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
logger: ILogger,
|
|
18
|
+
private readonly versionService: VersionService,
|
|
19
|
+
private readonly portService: PortService,
|
|
20
|
+
packageName: string = 'poet'
|
|
21
|
+
) {
|
|
22
|
+
super(logger, packageName);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
protected override async validate(_options: DevOptions): Promise<void> {
|
|
26
|
+
// Check Node.js version
|
|
27
|
+
const versionResult = this.versionService.checkNodeVersion();
|
|
28
|
+
|
|
29
|
+
if (!versionResult.isValid) {
|
|
30
|
+
throw new InvalidEnvironmentError(versionResult.message ?? 'Unsupported Node.js version');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Show warning if below recommended version
|
|
34
|
+
if (versionResult.hasWarning && versionResult.message) {
|
|
35
|
+
this.logger.warn(versionResult.message);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
protected override async execute(options: DevOptions): Promise<void> {
|
|
40
|
+
// Find available port
|
|
41
|
+
const port = await this.portService.findAvailablePort(options.port);
|
|
42
|
+
|
|
43
|
+
// Get CLI version
|
|
44
|
+
const cliVersion = this.versionService.getCliVersion();
|
|
45
|
+
|
|
46
|
+
// Start development server (delegate to @poetora/previewing)
|
|
47
|
+
// Convert DevOptions to ArgumentsCamelCase format expected by dev()
|
|
48
|
+
const devArgs: ArgumentsCamelCase = {
|
|
49
|
+
_: [],
|
|
50
|
+
$0: this.packageName,
|
|
51
|
+
port,
|
|
52
|
+
open: options.open ?? true,
|
|
53
|
+
localSchema: options.localSchema ?? false,
|
|
54
|
+
clientVersion: options.clientVersion,
|
|
55
|
+
groups: options.groups,
|
|
56
|
+
disableOpenapi: options.disableOpenapi ?? false,
|
|
57
|
+
packageName: this.packageName,
|
|
58
|
+
cliVersion,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
await dev(devArgs);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { input, select } from '@inquirer/prompts';
|
|
2
|
+
import { ValidationError } from '../errors/index.js';
|
|
3
|
+
import type { TemplateService } from '../services/template.service.js';
|
|
4
|
+
import type { InitOptions } from '../types/index.js';
|
|
5
|
+
import type { ILogger } from '../utils/index.js';
|
|
6
|
+
import { BaseCommand } from './base.command.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Init command - Create a new Poetora documentation site
|
|
10
|
+
*/
|
|
11
|
+
export class InitCommand extends BaseCommand {
|
|
12
|
+
readonly name = 'init';
|
|
13
|
+
readonly description = 'Create a new Poetora documentation site';
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
logger: ILogger,
|
|
17
|
+
private readonly templateService: TemplateService,
|
|
18
|
+
packageName: string = 'poet'
|
|
19
|
+
) {
|
|
20
|
+
super(logger, packageName);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
protected override async execute(options: InitOptions): Promise<void> {
|
|
24
|
+
let installDir = options.directory;
|
|
25
|
+
|
|
26
|
+
// Step 1: Handle existing directory
|
|
27
|
+
const dirStatus = await this.templateService.checkDirectory(installDir);
|
|
28
|
+
|
|
29
|
+
if (dirStatus.exists && dirStatus.hasContents) {
|
|
30
|
+
const choice = await this.promptDirectoryChoice(installDir);
|
|
31
|
+
|
|
32
|
+
if (choice === 'cancel') {
|
|
33
|
+
this.logger.info('Installation cancelled');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (choice === 'subdir') {
|
|
38
|
+
const subdir = await this.promptSubdirectoryName();
|
|
39
|
+
installDir = installDir === '.' ? subdir : `${installDir}/${subdir}`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Step 2: Prompt for project configuration
|
|
44
|
+
const projectName = await this.promptProjectName(installDir);
|
|
45
|
+
const theme = await this.promptTheme();
|
|
46
|
+
|
|
47
|
+
// Step 3: Download and install template
|
|
48
|
+
this.logger.info('Setting up documentation project...');
|
|
49
|
+
|
|
50
|
+
await this.templateService.installTemplate({
|
|
51
|
+
directory: installDir,
|
|
52
|
+
projectName,
|
|
53
|
+
theme,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Step 4: Show success message
|
|
57
|
+
this.showOnboardingMessage(installDir);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private async promptDirectoryChoice(
|
|
61
|
+
directory: string
|
|
62
|
+
): Promise<'subdir' | 'overwrite' | 'cancel'> {
|
|
63
|
+
const choice = await select({
|
|
64
|
+
message: `Directory ${directory} is not empty. What would you like to do?`,
|
|
65
|
+
choices: [
|
|
66
|
+
{ name: 'Create in a subdirectory', value: 'subdir' },
|
|
67
|
+
{ name: 'Overwrite current directory (may lose contents)', value: 'overwrite' },
|
|
68
|
+
{ name: 'Cancel', value: 'cancel' },
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return choice as 'subdir' | 'overwrite' | 'cancel';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private async promptSubdirectoryName(): Promise<string> {
|
|
76
|
+
const subdir = await input({
|
|
77
|
+
message: 'Subdirectory name:',
|
|
78
|
+
default: 'docs',
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (!subdir || subdir.trim() === '') {
|
|
82
|
+
throw new ValidationError('Subdirectory name cannot be empty');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return subdir.trim();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private async promptProjectName(installDir: string): Promise<string> {
|
|
89
|
+
const defaultProject = installDir === '.' ? 'Poetora' : installDir;
|
|
90
|
+
|
|
91
|
+
const projectName = await input({
|
|
92
|
+
message: 'Project Name',
|
|
93
|
+
default: defaultProject,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return projectName || defaultProject;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private async promptTheme(): Promise<string> {
|
|
100
|
+
const themes = this.templateService.getAvailableThemes();
|
|
101
|
+
|
|
102
|
+
const theme = await select({
|
|
103
|
+
message: 'Theme',
|
|
104
|
+
choices: themes.map((t) => ({
|
|
105
|
+
name: t,
|
|
106
|
+
value: t,
|
|
107
|
+
})),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return theme;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private showOnboardingMessage(installDir: string): void {
|
|
114
|
+
this.logger.log('');
|
|
115
|
+
this.logger.success('Documentation Setup!');
|
|
116
|
+
this.logger.log('');
|
|
117
|
+
this.logger.log('To see your docs run:');
|
|
118
|
+
this.logger.log('');
|
|
119
|
+
if (installDir !== '.') {
|
|
120
|
+
this.logger.log(` cd ${installDir}`);
|
|
121
|
+
}
|
|
122
|
+
this.logger.log(` ${this.packageName} dev`);
|
|
123
|
+
this.logger.log('');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { LinkService } from '../services/index.js';
|
|
2
|
+
import type { RenameOptions } from '../types/index.js';
|
|
3
|
+
import type { ILogger } from '../utils/index.js';
|
|
4
|
+
import { BaseCommand } from './base.command.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Link command - Check broken links and rename files
|
|
8
|
+
*/
|
|
9
|
+
export class LinkCommand extends BaseCommand {
|
|
10
|
+
readonly name = 'link';
|
|
11
|
+
readonly description = 'manage documentation links';
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
logger: ILogger,
|
|
15
|
+
private readonly linkService: LinkService,
|
|
16
|
+
packageName: string = 'poet'
|
|
17
|
+
) {
|
|
18
|
+
super(logger, packageName);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Check for broken links
|
|
23
|
+
*/
|
|
24
|
+
async checkBrokenLinks(): Promise<Record<string, string[]>> {
|
|
25
|
+
return await this.linkService.checkBrokenLinks();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Rename file and update references
|
|
30
|
+
*/
|
|
31
|
+
async renameFile(options: RenameOptions): Promise<void> {
|
|
32
|
+
await this.linkService.renameFile(options.from, options.to, options.force);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
protected override async execute(): Promise<void> {
|
|
36
|
+
// Default action: check broken links
|
|
37
|
+
await this.linkService.checkBrokenLinks();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { UpdateService } from '../services/index.js';
|
|
2
|
+
import type { ILogger } from '../utils/index.js';
|
|
3
|
+
import { BaseCommand } from './base.command.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Update command - Update CLI to latest version
|
|
7
|
+
*/
|
|
8
|
+
export class UpdateCommand extends BaseCommand {
|
|
9
|
+
readonly name = 'update';
|
|
10
|
+
readonly description = 'update the CLI to the latest version';
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
logger: ILogger,
|
|
14
|
+
private readonly updateService: UpdateService,
|
|
15
|
+
packageName: string = 'poet'
|
|
16
|
+
) {
|
|
17
|
+
super(logger, packageName);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
protected override async execute(): Promise<void> {
|
|
21
|
+
await this.updateService.update();
|
|
22
|
+
}
|
|
23
|
+
}
|