@poetora/cli 0.1.9 → 0.1.11

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.
Files changed (51) hide show
  1. package/bin/cli-builder.js +32 -18
  2. package/bin/services/link.service.js +0 -11
  3. package/bin/services/version.service.d.ts +1 -1
  4. package/bin/services/version.service.js +12 -10
  5. package/bin/utils/index.d.ts +1 -0
  6. package/bin/utils/index.js +1 -0
  7. package/bin/utils/terminate.d.ts +1 -0
  8. package/bin/utils/terminate.js +4 -0
  9. package/package.json +5 -1
  10. package/.turbo/turbo-build.log +0 -4
  11. package/src/accessibility.ts +0 -180
  12. package/src/cli-builder.ts +0 -274
  13. package/src/cli.ts +0 -22
  14. package/src/commands/__tests__/base.command.test.ts +0 -139
  15. package/src/commands/__tests__/dev.command.test.ts +0 -241
  16. package/src/commands/__tests__/init.command.test.ts +0 -281
  17. package/src/commands/__tests__/utils.ts +0 -20
  18. package/src/commands/base.command.ts +0 -97
  19. package/src/commands/check.command.ts +0 -40
  20. package/src/commands/dev.command.ts +0 -63
  21. package/src/commands/index.ts +0 -6
  22. package/src/commands/init.command.ts +0 -125
  23. package/src/commands/link.command.ts +0 -39
  24. package/src/commands/update.command.ts +0 -23
  25. package/src/constants.ts +0 -4
  26. package/src/errors/cli-error.ts +0 -83
  27. package/src/errors/index.ts +0 -1
  28. package/src/index.ts +0 -110
  29. package/src/mdxAccessibility.ts +0 -132
  30. package/src/middlewares.ts +0 -73
  31. package/src/services/__tests__/port.service.test.ts +0 -83
  32. package/src/services/__tests__/template.service.test.ts +0 -234
  33. package/src/services/__tests__/version.service.test.ts +0 -165
  34. package/src/services/accessibility-check.service.ts +0 -226
  35. package/src/services/index.ts +0 -7
  36. package/src/services/link.service.ts +0 -65
  37. package/src/services/openapi-check.service.ts +0 -68
  38. package/src/services/port.service.ts +0 -47
  39. package/src/services/template.service.ts +0 -203
  40. package/src/services/update.service.ts +0 -76
  41. package/src/services/version.service.ts +0 -161
  42. package/src/start.ts +0 -6
  43. package/src/types/common.ts +0 -53
  44. package/src/types/index.ts +0 -2
  45. package/src/types/options.ts +0 -42
  46. package/src/utils/console-logger.ts +0 -123
  47. package/src/utils/index.ts +0 -2
  48. package/src/utils/logger.interface.ts +0 -70
  49. package/tsconfig.build.json +0 -17
  50. package/tsconfig.json +0 -21
  51. package/vitest.config.ts +0 -8
@@ -1,68 +0,0 @@
1
- import { getOpenApiDocumentFromUrl, isAllowedLocalSchemaUrl, validate } from '@poetora/shared';
2
- import * as fs from 'fs';
3
- import * as yaml from 'js-yaml';
4
-
5
- import { FileSystemError, ValidationError } from '../errors/index.js';
6
- import type { ILogger } from '../utils/index.js';
7
-
8
- /**
9
- * Service for OpenAPI validation
10
- */
11
- export class OpenApiCheckService {
12
- constructor(private readonly logger: ILogger) {}
13
-
14
- /**
15
- * Validate OpenAPI spec from URL or local file
16
- */
17
- async validateSpec(filename: string, localSchema: boolean = false): Promise<void> {
18
- // Check if it's a URL
19
- if (isAllowedLocalSchemaUrl(filename, localSchema)) {
20
- await getOpenApiDocumentFromUrl(filename);
21
- this.logger.success('OpenAPI definition is valid.');
22
- return;
23
- }
24
-
25
- // Warn for http:// URLs without --local-schema
26
- if (filename.startsWith('http://') && !localSchema) {
27
- this.logger.warn('include the --local-schema flag to check locally hosted OpenAPI files');
28
- this.logger.warn('only https protocol is supported in production');
29
- return;
30
- }
31
-
32
- // Read and validate local file
33
- const document = await this.readLocalOpenApiFile(filename);
34
-
35
- if (!document) {
36
- throw new ValidationError(
37
- 'failed to parse OpenAPI spec: could not parse file correctly, please check for any syntax errors.'
38
- );
39
- }
40
-
41
- await validate(document);
42
- this.logger.success('OpenAPI definition is valid.');
43
- }
44
-
45
- /**
46
- * Read OpenAPI file from local filesystem
47
- */
48
- private async readLocalOpenApiFile(filename: string): Promise<unknown> {
49
- try {
50
- const fileContents = await fs.promises.readFile(filename, 'utf-8');
51
-
52
- // Try to parse as JSON first
53
- if (filename.endsWith('.json')) {
54
- return JSON.parse(fileContents);
55
- }
56
-
57
- // Try YAML parsing
58
- return yaml.load(fileContents);
59
- } catch (error) {
60
- if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
61
- throw new FileSystemError(`file not found, please check the path provided: ${filename}`);
62
- }
63
- throw new ValidationError(
64
- `Failed to parse OpenAPI spec: ${error instanceof Error ? error.message : 'unknown error'}`
65
- );
66
- }
67
- }
68
- }
@@ -1,47 +0,0 @@
1
- import detect from 'detect-port';
2
-
3
- import { NoAvailablePortError } from '../errors/index.js';
4
- import { CLI_CONSTANTS } from '../types/index.js';
5
- import type { ILogger } from '../utils/index.js';
6
-
7
- /**
8
- * Service for handling port detection and availability checks
9
- */
10
- export class PortService {
11
- constructor(private readonly logger?: ILogger) {}
12
-
13
- /**
14
- * Find an available port starting from the preferred port
15
- * @param preferredPort - The preferred port to start from (defaults to 3000)
16
- * @returns The first available port found
17
- * @throws {NoAvailablePortError} if no port is available after MAX_ATTEMPTS tries
18
- */
19
- async findAvailablePort(preferredPort?: number): Promise<number> {
20
- const startPort = preferredPort ?? CLI_CONSTANTS.PORT.DEFAULT;
21
-
22
- for (let attempt = 0; attempt < CLI_CONSTANTS.PORT.MAX_ATTEMPTS; attempt++) {
23
- const port = startPort + attempt;
24
-
25
- if (await this.isPortAvailable(port)) {
26
- if (attempt > 0 && this.logger) {
27
- this.logger.info(`port ${startPort} is already in use. using ${port} instead`);
28
- }
29
- return port;
30
- }
31
- }
32
-
33
- throw new NoAvailablePortError(
34
- `No available port found after ${CLI_CONSTANTS.PORT.MAX_ATTEMPTS} attempts starting from ${startPort}`
35
- );
36
- }
37
-
38
- /**
39
- * Check if a specific port is available
40
- * @param port - The port number to check
41
- * @returns true if the port is available, false otherwise
42
- */
43
- private async isPortAvailable(port: number): Promise<boolean> {
44
- const detectedPort = await detect(port);
45
- return detectedPort === port;
46
- }
47
- }
@@ -1,203 +0,0 @@
1
- import { docsConfigSchema } from '@poetora/validation';
2
- import AdmZip from 'adm-zip';
3
- import * as fs from 'fs';
4
- import * as fse from 'fs-extra';
5
- import * as path from 'path';
6
-
7
- import { ExternalServiceError, FileSystemError } from '../errors/index.js';
8
-
9
- export interface DirectoryStatus {
10
- exists: boolean;
11
- hasContents: boolean;
12
- }
13
-
14
- export interface InstallTemplateParams {
15
- directory: string;
16
- projectName: string;
17
- theme: string;
18
- }
19
-
20
- interface DocsConfig {
21
- name: string;
22
- theme: string;
23
- [key: string]: unknown;
24
- }
25
-
26
- /**
27
- * Service for managing documentation templates
28
- */
29
- export class TemplateService {
30
- private readonly TEMPLATE_URL = 'https://github.com/poetora/starter/archive/refs/heads/main.zip';
31
- private readonly TEMP_ZIP_PATH = 'poetora-starter.zip';
32
- private readonly TEMP_EXTRACT_DIR = 'poetora-starter-temp';
33
-
34
- /**
35
- * Check if directory exists and has contents
36
- */
37
- async checkDirectory(directory: string): Promise<DirectoryStatus> {
38
- try {
39
- await fse.ensureDir(directory);
40
-
41
- const files = await fs.promises.readdir(directory);
42
- const hasContents = files.length > 0;
43
-
44
- return {
45
- exists: true,
46
- hasContents,
47
- };
48
- } catch (error) {
49
- if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
50
- return {
51
- exists: false,
52
- hasContents: false,
53
- };
54
- }
55
- throw new FileSystemError(`Failed to check directory: ${(error as Error).message}`);
56
- }
57
- }
58
-
59
- /**
60
- * Get available themes from validation package
61
- */
62
- getAvailableThemes(): string[] {
63
- // docsConfigSchema is a discriminated union with 'theme' as the discriminator
64
- // Extract theme values from each option's shape.theme field
65
- const themes = docsConfigSchema.options
66
- .map((option) => {
67
- // Each option has a shape with theme field (ZodLiteral)
68
- const themeField = (
69
- option as unknown as { shape?: { theme?: { _def?: { value?: string } } } }
70
- ).shape?.theme;
71
- if (themeField?._def && 'value' in themeField._def) {
72
- return themeField._def.value as string;
73
- }
74
- return null;
75
- })
76
- .filter((theme): theme is string => theme !== null);
77
-
78
- return themes.length > 0 ? themes : ['ora'];
79
- }
80
-
81
- /**
82
- * Download and install template to target directory
83
- */
84
- async installTemplate(params: InstallTemplateParams): Promise<void> {
85
- const { directory, projectName, theme } = params;
86
-
87
- try {
88
- // Download template
89
- await this.downloadTemplate();
90
-
91
- // Extract template
92
- await this.extractTemplate();
93
-
94
- // Copy files to target directory
95
- await this.copyTemplateFiles(directory);
96
-
97
- // Update configuration
98
- await this.updateDocsConfig(directory, projectName, theme);
99
-
100
- // Cleanup temporary files
101
- await this.cleanup();
102
- } catch (error) {
103
- // Ensure cleanup on error
104
- await this.cleanup();
105
- throw error;
106
- }
107
- }
108
-
109
- private async downloadTemplate(): Promise<void> {
110
- try {
111
- const response = await fetch(this.TEMPLATE_URL);
112
-
113
- if (!response.ok) {
114
- throw new ExternalServiceError(
115
- `Failed to download template: HTTP ${response.status} ${response.statusText}`
116
- );
117
- }
118
-
119
- const arrayBuffer = await response.arrayBuffer();
120
- const buffer = Buffer.from(arrayBuffer);
121
-
122
- await fs.promises.writeFile(this.TEMP_ZIP_PATH, buffer);
123
- } catch (error) {
124
- if (error instanceof ExternalServiceError) {
125
- throw error;
126
- }
127
- throw new ExternalServiceError(`Failed to download template: ${(error as Error).message}`);
128
- }
129
- }
130
-
131
- private async extractTemplate(): Promise<void> {
132
- try {
133
- const zip = new AdmZip(this.TEMP_ZIP_PATH);
134
- zip.extractAllTo(this.TEMP_EXTRACT_DIR, true);
135
- } catch (error) {
136
- throw new FileSystemError(`Failed to extract template: ${(error as Error).message}`);
137
- }
138
- }
139
-
140
- private async copyTemplateFiles(targetDir: string): Promise<void> {
141
- try {
142
- // The extracted folder is typically named "starter-main"
143
- const extractedFolder = path.join(this.TEMP_EXTRACT_DIR, 'starter-main');
144
-
145
- // Ensure target directory exists
146
- await fse.ensureDir(targetDir);
147
-
148
- // Copy all files from extracted folder to target directory
149
- await fse.copy(extractedFolder, targetDir, {
150
- overwrite: true,
151
- errorOnExist: false,
152
- });
153
- } catch (error) {
154
- throw new FileSystemError(`Failed to copy template files: ${(error as Error).message}`);
155
- }
156
- }
157
-
158
- private async updateDocsConfig(
159
- directory: string,
160
- projectName: string,
161
- theme: string
162
- ): Promise<void> {
163
- try {
164
- const configPath = path.join(directory, 'docs.json');
165
-
166
- let config: DocsConfig;
167
-
168
- // Try to read existing config
169
- try {
170
- const configContent = await fs.promises.readFile(configPath, 'utf-8');
171
- config = JSON.parse(configContent) as DocsConfig;
172
- } catch {
173
- // If file doesn't exist or is invalid, create new config
174
- config = {} as DocsConfig;
175
- }
176
-
177
- // Update config with user choices
178
- config.name = projectName;
179
- config.theme = theme;
180
-
181
- // Write updated config
182
- await fs.promises.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
183
- } catch (error) {
184
- throw new FileSystemError(`Failed to update docs.json: ${(error as Error).message}`);
185
- }
186
- }
187
-
188
- private async cleanup(): Promise<void> {
189
- try {
190
- // Remove temporary files
191
- await Promise.all([
192
- fse.remove(this.TEMP_ZIP_PATH).catch(() => {
193
- /* ignore */
194
- }),
195
- fse.remove(this.TEMP_EXTRACT_DIR).catch(() => {
196
- /* ignore */
197
- }),
198
- ]);
199
- } catch {
200
- // Ignore cleanup errors
201
- }
202
- }
203
- }
@@ -1,76 +0,0 @@
1
- import { exec } from 'child_process';
2
- import { promisify } from 'util';
3
- import { ExternalServiceError } from '../errors/index.js';
4
- import type { ILogger } from '../utils/index.js';
5
- import type { VersionService } from './version.service.js';
6
-
7
- const execAsync = promisify(exec);
8
-
9
- /**
10
- * Service for CLI updates
11
- */
12
- export class UpdateService {
13
- constructor(
14
- private readonly logger: ILogger,
15
- private readonly versionService: VersionService,
16
- private readonly packageName: string
17
- ) {}
18
-
19
- /**
20
- * Update CLI to latest version
21
- */
22
- async update(): Promise<void> {
23
- const spinner = this.logger.spinner('checking for updates...');
24
- spinner.start();
25
-
26
- const existingCliVersion = this.versionService.getCliVersion();
27
- const latestCliVersion = this.versionService.getLatestCliVersion(this.packageName);
28
-
29
- const isUpToDate =
30
- existingCliVersion &&
31
- latestCliVersion &&
32
- latestCliVersion.trim() === existingCliVersion.trim();
33
-
34
- if (isUpToDate) {
35
- spinner.succeed('already up to date');
36
- return;
37
- }
38
-
39
- if (existingCliVersion && latestCliVersion.trim() !== existingCliVersion.trim()) {
40
- try {
41
- spinner.stop();
42
- const updateSpinner = this.logger.spinner(`updating ${this.packageName} package...`);
43
- updateSpinner.start();
44
-
45
- const packageManager = await this.detectPackageManager();
46
-
47
- if (packageManager === 'pnpm') {
48
- await execAsync(`pnpm install -g ${this.packageName}@latest --silent`);
49
- } else {
50
- await execAsync(`npm install -g ${this.packageName}@latest --silent`);
51
- }
52
-
53
- updateSpinner.succeed(
54
- `updated ${this.packageName} to version ${this.logger.highlight?.(latestCliVersion) ?? latestCliVersion}`
55
- );
56
- } catch (_err) {
57
- throw new ExternalServiceError(`failed to update ${this.packageName}@latest`);
58
- }
59
- }
60
- }
61
-
62
- /**
63
- * Detect which package manager was used to install the CLI
64
- */
65
- private async detectPackageManager(): Promise<'npm' | 'pnpm'> {
66
- try {
67
- const { stdout: packagePath } = await execAsync(`which ${this.packageName}`);
68
- if (packagePath.includes('pnpm')) {
69
- return 'pnpm';
70
- }
71
- return 'npm';
72
- } catch (_error) {
73
- return 'npm';
74
- }
75
- }
76
- }
@@ -1,161 +0,0 @@
1
- import { getClientVersion, LOCAL_LINKED_CLI_VERSION } from '@poetora/previewing';
2
- import { execSync } from 'child_process';
3
- import yargs from 'yargs';
4
-
5
- import { InvalidEnvironmentError } from '../errors/index.js';
6
- import { CLI_CONSTANTS, type NodeVersion, type ValidationResult } from '../types/index.js';
7
-
8
- /**
9
- * Service for handling version checks and management
10
- */
11
- export class VersionService {
12
- constructor(private readonly packageName: string = 'poet') {}
13
- /**
14
- * Parse Node.js version string
15
- */
16
- parseNodeVersion(): NodeVersion {
17
- let versionString = process.version;
18
-
19
- // Remove 'v' prefix if present
20
- if (versionString.charAt(0) === 'v') {
21
- versionString = versionString.slice(1);
22
- }
23
-
24
- const parts = versionString.split('.');
25
- const major = parseInt(parts[0] ?? '0', 10);
26
- const minor = parseInt(parts[1] ?? '0', 10);
27
- const patch = parseInt(parts[2] ?? '0', 10);
28
-
29
- return {
30
- major,
31
- minor,
32
- patch,
33
- raw: versionString,
34
- };
35
- }
36
-
37
- /**
38
- * Check if current Node.js version is supported
39
- */
40
- checkNodeVersion(): ValidationResult {
41
- const current = this.parseNodeVersion();
42
- const { MIN_MAJOR, MIN_MINOR, MAX_MAJOR, RECOMMENDED_MAJOR, RECOMMENDED_MINOR } =
43
- CLI_CONSTANTS.NODE_VERSION;
44
-
45
- // Check if version is below minimum
46
- if (current.major < MIN_MAJOR || (current.major === MIN_MAJOR && current.minor < MIN_MINOR)) {
47
- return {
48
- isValid: false,
49
- hasWarning: false,
50
- message: `${this.packageName} requires Node.js ${MIN_MAJOR}.${MIN_MINOR}.0 or higher (current version ${current.raw}). Please upgrade Node.js and try again.`,
51
- };
52
- }
53
-
54
- // Check if version is above maximum
55
- if (current.major > MAX_MAJOR) {
56
- return {
57
- isValid: false,
58
- hasWarning: false,
59
- message: `${this.packageName} is not supported on Node.js ${current.major}.x. Please use an LTS version (${MIN_MAJOR}-${MAX_MAJOR}).`,
60
- };
61
- }
62
-
63
- // Check if version is below recommended
64
- if (
65
- current.major < RECOMMENDED_MAJOR ||
66
- (current.major === RECOMMENDED_MAJOR && current.minor < RECOMMENDED_MINOR)
67
- ) {
68
- return {
69
- isValid: true,
70
- hasWarning: true,
71
- message: `Node.js ${RECOMMENDED_MAJOR}.${RECOMMENDED_MINOR}.0 or higher is recommended for best compatibility (current: ${current.raw}).`,
72
- };
73
- }
74
-
75
- // Version is good
76
- return {
77
- isValid: true,
78
- hasWarning: false,
79
- };
80
- }
81
-
82
- /**
83
- * Get CLI version from package.json
84
- */
85
- getCliVersion(): string | undefined {
86
- const y = yargs();
87
- let version: string | undefined;
88
-
89
- y.showVersion((v) => {
90
- version = v;
91
- return false;
92
- });
93
-
94
- if (process.env.CLI_TEST_MODE === 'true') {
95
- return 'test-cli';
96
- }
97
-
98
- // When running npm link or pnpm link, the version is 'unknown'
99
- if (version === 'unknown') {
100
- version = LOCAL_LINKED_CLI_VERSION;
101
- }
102
-
103
- return version;
104
- }
105
-
106
- /**
107
- * Get client version
108
- */
109
- getClientVersion(): string {
110
- return getClientVersion().trim();
111
- }
112
-
113
- /**
114
- * Get versions of CLI and client
115
- */
116
- getVersions(): { cli: string | undefined; client: string } {
117
- return {
118
- cli: this.getCliVersion(),
119
- client: this.getClientVersion(),
120
- };
121
- }
122
-
123
- /**
124
- * Get latest CLI version from npm registry
125
- */
126
- getLatestCliVersion(packageName: string): string {
127
- try {
128
- const version = execSync(`npm view ${packageName} version --silent`, {
129
- encoding: 'utf-8',
130
- stdio: ['pipe', 'pipe', 'pipe'],
131
- }).trim();
132
-
133
- return version;
134
- } catch (error) {
135
- throw new Error(
136
- `Failed to fetch latest version for ${packageName}: ${
137
- error instanceof Error ? error.message : 'unknown error'
138
- }`
139
- );
140
- }
141
- }
142
-
143
- /**
144
- * Check if current version is up to date
145
- */
146
- isVersionUpToDate(currentVersion: string, latestVersion: string): boolean {
147
- return currentVersion.trim() === latestVersion.trim();
148
- }
149
-
150
- /**
151
- * Validate and throw if Node version is not supported
152
- * @throws {InvalidEnvironmentError} if Node version is not supported
153
- */
154
- validateNodeVersion(): void {
155
- const result = this.checkNodeVersion();
156
-
157
- if (!result.isValid) {
158
- throw new InvalidEnvironmentError(result.message ?? 'Unsupported Node.js version');
159
- }
160
- }
161
- }
package/src/start.ts DELETED
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env node
2
- import { cli } from './cli.js';
3
-
4
- const packageName = process.env.POETORA_PACKAGE_NAME ?? 'poet';
5
-
6
- cli({ packageName });
@@ -1,53 +0,0 @@
1
- /**
2
- * CLI constants
3
- */
4
- export const CLI_CONSTANTS = {
5
- PORT: {
6
- DEFAULT: 3000,
7
- MAX_ATTEMPTS: 10,
8
- },
9
- NODE_VERSION: {
10
- MIN_MAJOR: 18,
11
- MIN_MINOR: 0,
12
- MAX_MAJOR: 24,
13
- MAX_MINOR: Number.MAX_SAFE_INTEGER,
14
- RECOMMENDED_MAJOR: 20,
15
- RECOMMENDED_MINOR: 17,
16
- },
17
- TIMEOUT: {
18
- PROCESS_KILL: 5000,
19
- LOG_RENDER: 50,
20
- },
21
- UPDATE: {
22
- CHECK_INTERVAL: 86400000, // 24 hours
23
- },
24
- } as const;
25
-
26
- /**
27
- * Node version structure
28
- */
29
- export interface NodeVersion {
30
- major: number;
31
- minor: number;
32
- patch: number;
33
- raw: string;
34
- }
35
-
36
- /**
37
- * Validation result
38
- */
39
- export interface ValidationResult {
40
- isValid: boolean;
41
- hasWarning: boolean;
42
- message?: string;
43
- }
44
-
45
- /**
46
- * Logger spinner interface
47
- */
48
- export interface LoggerSpinner {
49
- start(): void;
50
- stop(): void;
51
- succeed(message?: string): void;
52
- fail(message?: string): void;
53
- }
@@ -1,2 +0,0 @@
1
- export * from './common.js';
2
- export * from './options.js';
@@ -1,42 +0,0 @@
1
- /**
2
- * Options for dev command
3
- */
4
- export interface DevOptions {
5
- port?: number;
6
- open?: boolean;
7
- localSchema?: boolean;
8
- clientVersion?: string;
9
- groups?: string[];
10
- disableOpenapi?: boolean;
11
- }
12
-
13
- /**
14
- * Options for init command
15
- */
16
- export interface InitOptions {
17
- directory: string;
18
- }
19
-
20
- /**
21
- * Options for openapi-check command
22
- */
23
- export interface OpenApiCheckOptions {
24
- filename: string;
25
- localSchema?: boolean;
26
- }
27
-
28
- /**
29
- * Options for rename command
30
- */
31
- export interface RenameOptions {
32
- from: string;
33
- to: string;
34
- force?: boolean;
35
- }
36
-
37
- /**
38
- * Options for update command
39
- */
40
- export interface UpdateOptions {
41
- packageName: string;
42
- }