@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.
- package/bin/cli-builder.js +32 -18
- package/bin/services/link.service.js +0 -11
- package/bin/services/version.service.d.ts +1 -1
- package/bin/services/version.service.js +12 -10
- package/bin/utils/index.d.ts +1 -0
- package/bin/utils/index.js +1 -0
- package/bin/utils/terminate.d.ts +1 -0
- package/bin/utils/terminate.js +4 -0
- package/package.json +5 -1
- package/.turbo/turbo-build.log +0 -4
- package/src/accessibility.ts +0 -180
- package/src/cli-builder.ts +0 -274
- package/src/cli.ts +0 -22
- package/src/commands/__tests__/base.command.test.ts +0 -139
- package/src/commands/__tests__/dev.command.test.ts +0 -241
- package/src/commands/__tests__/init.command.test.ts +0 -281
- package/src/commands/__tests__/utils.ts +0 -20
- package/src/commands/base.command.ts +0 -97
- package/src/commands/check.command.ts +0 -40
- package/src/commands/dev.command.ts +0 -63
- package/src/commands/index.ts +0 -6
- package/src/commands/init.command.ts +0 -125
- package/src/commands/link.command.ts +0 -39
- package/src/commands/update.command.ts +0 -23
- package/src/constants.ts +0 -4
- package/src/errors/cli-error.ts +0 -83
- package/src/errors/index.ts +0 -1
- package/src/index.ts +0 -110
- package/src/mdxAccessibility.ts +0 -132
- package/src/middlewares.ts +0 -73
- package/src/services/__tests__/port.service.test.ts +0 -83
- package/src/services/__tests__/template.service.test.ts +0 -234
- package/src/services/__tests__/version.service.test.ts +0 -165
- package/src/services/accessibility-check.service.ts +0 -226
- package/src/services/index.ts +0 -7
- package/src/services/link.service.ts +0 -65
- package/src/services/openapi-check.service.ts +0 -68
- package/src/services/port.service.ts +0 -47
- package/src/services/template.service.ts +0 -203
- package/src/services/update.service.ts +0 -76
- package/src/services/version.service.ts +0 -161
- package/src/start.ts +0 -6
- package/src/types/common.ts +0 -53
- package/src/types/index.ts +0 -2
- package/src/types/options.ts +0 -42
- package/src/utils/console-logger.ts +0 -123
- package/src/utils/index.ts +0 -2
- package/src/utils/logger.interface.ts +0 -70
- package/tsconfig.build.json +0 -17
- package/tsconfig.json +0 -21
- 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
package/src/types/common.ts
DELETED
|
@@ -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
|
-
}
|
package/src/types/index.ts
DELETED
package/src/types/options.ts
DELETED
|
@@ -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
|
-
}
|