@veloxts/cli 0.6.57 → 0.6.58
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/CHANGELOG.md +12 -0
- package/dist/cli.js +2 -0
- package/dist/commands/openapi.d.ts +11 -0
- package/dist/commands/openapi.js +205 -0
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @veloxts/cli
|
|
2
2
|
|
|
3
|
+
## 0.6.58
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- feat(router): add OpenAPI 3.0.3 specification generator
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @veloxts/auth@0.6.58
|
|
10
|
+
- @veloxts/core@0.6.58
|
|
11
|
+
- @veloxts/orm@0.6.58
|
|
12
|
+
- @veloxts/router@0.6.58
|
|
13
|
+
- @veloxts/validation@0.6.58
|
|
14
|
+
|
|
3
15
|
## 0.6.57
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ import { createIntrospectCommand } from './commands/introspect.js';
|
|
|
15
15
|
import { createMakeCommand } from './commands/make.js';
|
|
16
16
|
import { createMcpCommand } from './commands/mcp.js';
|
|
17
17
|
import { createMigrateCommand } from './commands/migrate.js';
|
|
18
|
+
import { createOpenApiCommand } from './commands/openapi.js';
|
|
18
19
|
import { createProceduresCommand } from './commands/procedures.js';
|
|
19
20
|
import { createScheduleCommand } from './commands/schedule.js';
|
|
20
21
|
import { createTenantCommand } from './commands/tenant.js';
|
|
@@ -36,6 +37,7 @@ function createCLI() {
|
|
|
36
37
|
program.addCommand(createMakeCommand());
|
|
37
38
|
program.addCommand(createMcpCommand());
|
|
38
39
|
program.addCommand(createMigrateCommand());
|
|
40
|
+
program.addCommand(createOpenApiCommand());
|
|
39
41
|
program.addCommand(createProceduresCommand());
|
|
40
42
|
program.addCommand(createScheduleCommand());
|
|
41
43
|
program.addCommand(createTenantCommand());
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAPI command - Generate OpenAPI specifications
|
|
3
|
+
*
|
|
4
|
+
* Provides subcommands for generating OpenAPI documentation:
|
|
5
|
+
* - openapi:generate - Generate OpenAPI JSON specification from procedures
|
|
6
|
+
*/
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
/**
|
|
9
|
+
* Create the openapi command with subcommands
|
|
10
|
+
*/
|
|
11
|
+
export declare function createOpenApiCommand(): Command;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAPI command - Generate OpenAPI specifications
|
|
3
|
+
*
|
|
4
|
+
* Provides subcommands for generating OpenAPI documentation:
|
|
5
|
+
* - openapi:generate - Generate OpenAPI JSON specification from procedures
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { mkdir } from 'node:fs/promises';
|
|
9
|
+
import { dirname, resolve } from 'node:path';
|
|
10
|
+
import { discoverProceduresVerbose, generateOpenApiSpec, isDiscoveryError, validateOpenApiSpec, } from '@veloxts/router';
|
|
11
|
+
import { Command } from 'commander';
|
|
12
|
+
import { config as loadEnv } from 'dotenv';
|
|
13
|
+
import pc from 'picocolors';
|
|
14
|
+
/**
|
|
15
|
+
* Load environment variables from .env file if present
|
|
16
|
+
*/
|
|
17
|
+
function loadEnvironment() {
|
|
18
|
+
const envPath = resolve(process.cwd(), '.env');
|
|
19
|
+
if (existsSync(envPath)) {
|
|
20
|
+
loadEnv({ path: envPath });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Helper Functions
|
|
25
|
+
// ============================================================================
|
|
26
|
+
/**
|
|
27
|
+
* Parse server URLs into OpenAPI Server objects
|
|
28
|
+
*/
|
|
29
|
+
function parseServers(servers) {
|
|
30
|
+
if (!servers || servers.length === 0) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
return servers.map((server, index) => {
|
|
34
|
+
// Support "url|description" format
|
|
35
|
+
const [url, description] = server.split('|');
|
|
36
|
+
return {
|
|
37
|
+
url: url.trim(),
|
|
38
|
+
description: description?.trim() ?? (index === 0 ? 'Primary server' : undefined),
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Ensure directory exists for output file
|
|
44
|
+
*/
|
|
45
|
+
async function ensureDir(filePath) {
|
|
46
|
+
const dir = dirname(filePath);
|
|
47
|
+
if (!existsSync(dir)) {
|
|
48
|
+
await mkdir(dir, { recursive: true });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Print success message with summary
|
|
53
|
+
*/
|
|
54
|
+
function printSuccess(outputPath, spec, warnings, quiet) {
|
|
55
|
+
if (quiet) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const pathCount = Object.keys(spec.paths).length;
|
|
59
|
+
const tagCount = spec.tags?.length ?? 0;
|
|
60
|
+
console.log();
|
|
61
|
+
console.log(pc.green('✓') + pc.bold(' OpenAPI specification generated'));
|
|
62
|
+
console.log();
|
|
63
|
+
console.log(` ${pc.dim('Output:')} ${outputPath}`);
|
|
64
|
+
console.log(` ${pc.dim('Title:')} ${spec.info.title}`);
|
|
65
|
+
console.log(` ${pc.dim('Version:')} ${spec.info.version}`);
|
|
66
|
+
console.log(` ${pc.dim('Paths:')} ${pathCount}`);
|
|
67
|
+
console.log(` ${pc.dim('Tags:')} ${tagCount}`);
|
|
68
|
+
if (spec.servers?.length) {
|
|
69
|
+
console.log(` ${pc.dim('Servers:')} ${spec.servers.map((s) => s.url).join(', ')}`);
|
|
70
|
+
}
|
|
71
|
+
if (warnings.length > 0) {
|
|
72
|
+
console.log();
|
|
73
|
+
console.log(pc.yellow(`${warnings.length} warning(s):`));
|
|
74
|
+
for (const warning of warnings) {
|
|
75
|
+
console.log(pc.yellow(` • ${warning}`));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
console.log();
|
|
79
|
+
}
|
|
80
|
+
// ============================================================================
|
|
81
|
+
// Command Implementation
|
|
82
|
+
// ============================================================================
|
|
83
|
+
/**
|
|
84
|
+
* Create the openapi:generate command
|
|
85
|
+
*/
|
|
86
|
+
function createGenerateCommand() {
|
|
87
|
+
return new Command('generate')
|
|
88
|
+
.description('Generate OpenAPI specification from procedures')
|
|
89
|
+
.option('-p, --path <path>', 'Path to procedures directory', './src/procedures')
|
|
90
|
+
.option('-o, --output <file>', 'Output file path', './openapi.json')
|
|
91
|
+
.option('-t, --title <title>', 'API title', 'VeloxTS API')
|
|
92
|
+
.option('-V, --version <version>', 'API version', '1.0.0')
|
|
93
|
+
.option('-d, --description <desc>', 'API description')
|
|
94
|
+
.option('-s, --server <url>', 'Server URL (can be specified multiple times)', collectOption)
|
|
95
|
+
.option('--prefix <prefix>', 'API route prefix', '/api')
|
|
96
|
+
.option('-r, --recursive', 'Scan subdirectories for procedures', false)
|
|
97
|
+
.option('--pretty', 'Pretty-print JSON output', true)
|
|
98
|
+
.option('--no-pretty', 'Minify JSON output')
|
|
99
|
+
.option('--validate', 'Validate generated spec for issues', true)
|
|
100
|
+
.option('--no-validate', 'Skip validation')
|
|
101
|
+
.option('-q, --quiet', 'Suppress output except errors', false)
|
|
102
|
+
.action(async (options) => {
|
|
103
|
+
// Load .env file before importing procedure files
|
|
104
|
+
loadEnvironment();
|
|
105
|
+
const proceduresPath = options.path ?? './src/procedures';
|
|
106
|
+
const outputPath = resolve(process.cwd(), options.output ?? './openapi.json');
|
|
107
|
+
const discoveryOptions = {
|
|
108
|
+
recursive: options.recursive ?? false,
|
|
109
|
+
onInvalidExport: 'warn',
|
|
110
|
+
};
|
|
111
|
+
try {
|
|
112
|
+
// Discover procedures
|
|
113
|
+
if (!options.quiet) {
|
|
114
|
+
console.log(pc.dim('Discovering procedures...'));
|
|
115
|
+
}
|
|
116
|
+
const discovery = await discoverProceduresVerbose(proceduresPath, discoveryOptions);
|
|
117
|
+
if (discovery.collections.length === 0) {
|
|
118
|
+
console.error(pc.red('Error: No procedure collections found'));
|
|
119
|
+
console.error(pc.dim(`Searched in: ${proceduresPath}`));
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
if (!options.quiet) {
|
|
123
|
+
console.log(pc.dim(`Found ${discovery.collections.length} collection(s) with ` +
|
|
124
|
+
`${discovery.collections.reduce((sum, c) => sum + Object.keys(c.procedures).length, 0)} procedure(s)`));
|
|
125
|
+
}
|
|
126
|
+
// Build OpenAPI options
|
|
127
|
+
const openApiOptions = {
|
|
128
|
+
info: {
|
|
129
|
+
title: options.title ?? 'VeloxTS API',
|
|
130
|
+
version: options.version ?? '1.0.0',
|
|
131
|
+
description: options.description,
|
|
132
|
+
},
|
|
133
|
+
prefix: options.prefix ?? '/api',
|
|
134
|
+
servers: parseServers(options.server),
|
|
135
|
+
};
|
|
136
|
+
// Generate spec
|
|
137
|
+
if (!options.quiet) {
|
|
138
|
+
console.log(pc.dim('Generating OpenAPI specification...'));
|
|
139
|
+
}
|
|
140
|
+
const spec = generateOpenApiSpec(discovery.collections, openApiOptions);
|
|
141
|
+
// Validate if requested
|
|
142
|
+
const warnings = [];
|
|
143
|
+
if (options.validate !== false) {
|
|
144
|
+
const validationWarnings = validateOpenApiSpec(spec);
|
|
145
|
+
warnings.push(...validationWarnings);
|
|
146
|
+
}
|
|
147
|
+
// Add discovery warnings
|
|
148
|
+
warnings.push(...discovery.warnings.map((w) => `${w.filePath}: ${w.message}`));
|
|
149
|
+
// Write output
|
|
150
|
+
await ensureDir(outputPath);
|
|
151
|
+
const jsonContent = options.pretty !== false ? JSON.stringify(spec, null, 2) : JSON.stringify(spec);
|
|
152
|
+
writeFileSync(outputPath, jsonContent, 'utf-8');
|
|
153
|
+
// Print success message
|
|
154
|
+
printSuccess(outputPath, spec, warnings, options.quiet ?? false);
|
|
155
|
+
// Exit explicitly - dynamic imports may keep event loop running
|
|
156
|
+
process.exit(0);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
if (isDiscoveryError(error)) {
|
|
160
|
+
console.error(pc.red(error.format()));
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Collect multiple option values into array
|
|
169
|
+
*/
|
|
170
|
+
function collectOption(value, previous = []) {
|
|
171
|
+
return [...previous, value];
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Create the openapi:serve command (placeholder for future Swagger UI serving)
|
|
175
|
+
*/
|
|
176
|
+
function createServeCommand() {
|
|
177
|
+
return new Command('serve')
|
|
178
|
+
.description('Start a local Swagger UI server (coming soon)')
|
|
179
|
+
.option('-f, --file <file>', 'OpenAPI spec file', './openapi.json')
|
|
180
|
+
.option('--port <port>', 'Server port', '8080')
|
|
181
|
+
.action((_options) => {
|
|
182
|
+
console.log(pc.yellow('The serve command will be available in a future version.'));
|
|
183
|
+
console.log(pc.dim('For now, use the swaggerUIPlugin in your Fastify app:'));
|
|
184
|
+
console.log();
|
|
185
|
+
console.log(pc.cyan(` import { swaggerUIPlugin } from '@veloxts/router';`));
|
|
186
|
+
console.log();
|
|
187
|
+
console.log(pc.cyan(` app.register(swaggerUIPlugin, {`));
|
|
188
|
+
console.log(pc.cyan(` routePrefix: '/docs',`));
|
|
189
|
+
console.log(pc.cyan(` collections: [userProcedures],`));
|
|
190
|
+
console.log(pc.cyan(` openapi: { info: { title: 'My API', version: '1.0.0' } },`));
|
|
191
|
+
console.log(pc.cyan(` });`));
|
|
192
|
+
console.log();
|
|
193
|
+
process.exit(0);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Create the openapi command with subcommands
|
|
198
|
+
*/
|
|
199
|
+
export function createOpenApiCommand() {
|
|
200
|
+
const openapi = new Command('openapi')
|
|
201
|
+
.description('OpenAPI specification generation and management')
|
|
202
|
+
.addCommand(createGenerateCommand())
|
|
203
|
+
.addCommand(createServeCommand());
|
|
204
|
+
return openapi;
|
|
205
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veloxts/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.58",
|
|
4
4
|
"description": "Developer tooling and CLI commands for VeloxTS framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -40,11 +40,11 @@
|
|
|
40
40
|
"picocolors": "1.1.1",
|
|
41
41
|
"pluralize": "8.0.0",
|
|
42
42
|
"tsx": "4.21.0",
|
|
43
|
-
"@veloxts/
|
|
44
|
-
"@veloxts/
|
|
45
|
-
"@veloxts/
|
|
46
|
-
"@veloxts/
|
|
47
|
-
"@veloxts/validation": "0.6.
|
|
43
|
+
"@veloxts/auth": "0.6.58",
|
|
44
|
+
"@veloxts/core": "0.6.58",
|
|
45
|
+
"@veloxts/router": "0.6.58",
|
|
46
|
+
"@veloxts/orm": "0.6.58",
|
|
47
|
+
"@veloxts/validation": "0.6.58"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@prisma/client": ">=7.0.0"
|