@veloxts/cli 0.6.84 → 0.6.86

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 CHANGED
@@ -1,5 +1,29 @@
1
1
  # @veloxts/cli
2
2
 
3
+ ## 0.6.86
4
+
5
+ ### Patch Changes
6
+
7
+ - updated documentation
8
+ - Updated dependencies
9
+ - @veloxts/auth@0.6.86
10
+ - @veloxts/core@0.6.86
11
+ - @veloxts/orm@0.6.86
12
+ - @veloxts/router@0.6.86
13
+ - @veloxts/validation@0.6.86
14
+
15
+ ## 0.6.85
16
+
17
+ ### Patch Changes
18
+
19
+ - implement missing features from original requirements
20
+ - Updated dependencies
21
+ - @veloxts/auth@0.6.85
22
+ - @veloxts/core@0.6.85
23
+ - @veloxts/orm@0.6.85
24
+ - @veloxts/router@0.6.85
25
+ - @veloxts/validation@0.6.85
26
+
3
27
  ## 0.6.84
4
28
 
5
29
  ### Patch Changes
package/GUIDE.md CHANGED
@@ -111,6 +111,105 @@ Supported platforms:
111
111
  - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
112
112
  - Linux: `~/.config/Claude/claude_desktop_config.json`
113
113
 
114
+ ### velox openapi generate
115
+
116
+ Generate OpenAPI 3.0.3 specification from procedure definitions:
117
+
118
+ ```bash
119
+ # Basic usage - JSON to ./openapi.json
120
+ velox openapi generate
121
+
122
+ # YAML output (auto-detected from extension)
123
+ velox openapi generate -o ./docs/api.yaml
124
+
125
+ # Full configuration
126
+ velox openapi generate \
127
+ --path ./src/procedures \
128
+ --output ./docs/openapi.json \
129
+ --title "My API" \
130
+ --version "2.0.0" \
131
+ --description "Production API documentation" \
132
+ --server "http://localhost:3030|Development" \
133
+ --server "https://api.example.com|Production" \
134
+ --prefix /api \
135
+ --recursive \
136
+ --pretty
137
+ ```
138
+
139
+ **Options:**
140
+
141
+ | Option | Description | Default |
142
+ |--------|-------------|---------|
143
+ | `-p, --path <path>` | Procedures directory | `./src/procedures` |
144
+ | `-o, --output <file>` | Output file path | `./openapi.json` |
145
+ | `-f, --format <format>` | Output format: `json` or `yaml` | Auto-detected from extension |
146
+ | `-t, --title <title>` | API title | `VeloxTS API` |
147
+ | `-V, --version <version>` | API version | `1.0.0` |
148
+ | `-d, --description <desc>` | API description | None |
149
+ | `-s, --server <url>` | Server URL (repeatable, format: `url\|description`) | None |
150
+ | `--prefix <prefix>` | API route prefix | `/api` |
151
+ | `-r, --recursive` | Scan subdirectories for procedures | `false` |
152
+ | `--pretty` / `--no-pretty` | Pretty-print or minify output | `true` |
153
+ | `--validate` / `--no-validate` | Validate generated spec for issues | `true` |
154
+ | `-q, --quiet` | Suppress output except errors | `false` |
155
+
156
+ **Server URL Format:**
157
+
158
+ You can specify multiple server URLs using the `url|description` format:
159
+
160
+ ```bash
161
+ velox openapi generate \
162
+ -s "http://localhost:3030|Local development" \
163
+ -s "https://staging.example.com|Staging environment" \
164
+ -s "https://api.example.com|Production"
165
+ ```
166
+
167
+ **Output:**
168
+
169
+ The command generates a complete OpenAPI 3.0.3 specification including:
170
+ - Auto-generated paths from procedure naming conventions
171
+ - Request/response schemas from Zod definitions
172
+ - Security schemes from guards (JWT, API keys, etc.)
173
+ - Deprecation warnings from `.deprecated()` procedures
174
+ - Field descriptions from Zod `.describe()` calls
175
+
176
+ ### velox openapi serve
177
+
178
+ Start a local Swagger UI server to preview OpenAPI documentation:
179
+
180
+ ```bash
181
+ # Serve with default settings
182
+ velox openapi serve
183
+
184
+ # Custom spec file and port
185
+ velox openapi serve -f ./docs/api.yaml --port 9000
186
+
187
+ # Enable hot-reload on file changes
188
+ velox openapi serve --watch
189
+
190
+ # Bind to all interfaces (accessible from network)
191
+ velox openapi serve --host 0.0.0.0
192
+ ```
193
+
194
+ **Options:**
195
+
196
+ | Option | Description | Default |
197
+ |--------|-------------|---------|
198
+ | `-f, --file <file>` | OpenAPI spec file (JSON or YAML) | `./openapi.json` |
199
+ | `--port <port>` | Server port | `8080` |
200
+ | `--host <host>` | Host to bind | `localhost` |
201
+ | `-w, --watch` | Watch for file changes and hot-reload | `false` |
202
+
203
+ **Features:**
204
+ - Interactive Swagger UI interface
205
+ - Try out API endpoints directly from the browser
206
+ - Auto-reload when spec file changes (with `--watch`)
207
+ - CORS enabled for development
208
+
209
+ The server provides two endpoints:
210
+ - `/` - Swagger UI interface
211
+ - `/openapi.json` - Raw OpenAPI specification (always JSON, regardless of source format)
212
+
114
213
  ## Database Seeding
115
214
 
116
215
  ### Creating Seeders
@@ -1,8 +1,9 @@
1
1
  /**
2
- * OpenAPI command - Generate OpenAPI specifications
2
+ * OpenAPI command - Generate and serve OpenAPI specifications
3
3
  *
4
- * Provides subcommands for generating OpenAPI documentation:
5
- * - openapi:generate - Generate OpenAPI JSON specification from procedures
4
+ * Provides subcommands for generating and serving OpenAPI documentation:
5
+ * - openapi generate - Generate OpenAPI JSON/YAML specification from procedures
6
+ * - openapi serve - Start a local Swagger UI server
6
7
  */
7
8
  import { Command } from 'commander';
8
9
  /**
@@ -1,16 +1,19 @@
1
1
  /**
2
- * OpenAPI command - Generate OpenAPI specifications
2
+ * OpenAPI command - Generate and serve OpenAPI specifications
3
3
  *
4
- * Provides subcommands for generating OpenAPI documentation:
5
- * - openapi:generate - Generate OpenAPI JSON specification from procedures
4
+ * Provides subcommands for generating and serving OpenAPI documentation:
5
+ * - openapi generate - Generate OpenAPI JSON/YAML specification from procedures
6
+ * - openapi serve - Start a local Swagger UI server
6
7
  */
7
- import { existsSync, writeFileSync } from 'node:fs';
8
+ import { existsSync, readFileSync, watch, writeFileSync } from 'node:fs';
8
9
  import { mkdir } from 'node:fs/promises';
9
- import { dirname, resolve } from 'node:path';
10
- import { discoverProceduresVerbose, generateOpenApiSpec, isDiscoveryError, validateOpenApiSpec, } from '@veloxts/router';
10
+ import { createServer } from 'node:http';
11
+ import { dirname, extname, resolve } from 'node:path';
12
+ import { discoverProceduresVerbose, generateOpenApiSpec, generateSwaggerUIHtml, isDiscoveryError, validateOpenApiSpec, } from '@veloxts/router';
11
13
  import { Command } from 'commander';
12
14
  import { config as loadEnv } from 'dotenv';
13
15
  import pc from 'picocolors';
16
+ import YAML from 'yaml';
14
17
  /**
15
18
  * Load environment variables from .env file if present
16
19
  */
@@ -23,6 +26,28 @@ function loadEnvironment() {
23
26
  // ============================================================================
24
27
  // Helper Functions
25
28
  // ============================================================================
29
+ /**
30
+ * Detect output format from file extension
31
+ */
32
+ function detectFormat(outputPath, explicitFormat) {
33
+ if (explicitFormat) {
34
+ return explicitFormat;
35
+ }
36
+ const ext = extname(outputPath).toLowerCase();
37
+ if (ext === '.yaml' || ext === '.yml') {
38
+ return 'yaml';
39
+ }
40
+ return 'json';
41
+ }
42
+ /**
43
+ * Serialize OpenAPI spec to string
44
+ */
45
+ function serializeSpec(spec, format, pretty) {
46
+ if (format === 'yaml') {
47
+ return YAML.stringify(spec, { indent: 2 });
48
+ }
49
+ return pretty ? JSON.stringify(spec, null, 2) : JSON.stringify(spec);
50
+ }
26
51
  /**
27
52
  * Parse server URLs into OpenAPI Server objects
28
53
  */
@@ -51,7 +76,7 @@ async function ensureDir(filePath) {
51
76
  /**
52
77
  * Print success message with summary
53
78
  */
54
- function printSuccess(outputPath, spec, warnings, quiet) {
79
+ function printSuccess(outputPath, spec, warnings, quiet, format = 'json') {
55
80
  if (quiet) {
56
81
  return;
57
82
  }
@@ -61,6 +86,7 @@ function printSuccess(outputPath, spec, warnings, quiet) {
61
86
  console.log(pc.green('✓') + pc.bold(' OpenAPI specification generated'));
62
87
  console.log();
63
88
  console.log(` ${pc.dim('Output:')} ${outputPath}`);
89
+ console.log(` ${pc.dim('Format:')} ${format.toUpperCase()}`);
64
90
  console.log(` ${pc.dim('Title:')} ${spec.info.title}`);
65
91
  console.log(` ${pc.dim('Version:')} ${spec.info.version}`);
66
92
  console.log(` ${pc.dim('Paths:')} ${pathCount}`);
@@ -88,14 +114,15 @@ function createGenerateCommand() {
88
114
  .description('Generate OpenAPI specification from procedures')
89
115
  .option('-p, --path <path>', 'Path to procedures directory', './src/procedures')
90
116
  .option('-o, --output <file>', 'Output file path', './openapi.json')
117
+ .option('-f, --format <format>', 'Output format (json or yaml), auto-detected from file extension if not specified')
91
118
  .option('-t, --title <title>', 'API title', 'VeloxTS API')
92
119
  .option('-V, --version <version>', 'API version', '1.0.0')
93
120
  .option('-d, --description <desc>', 'API description')
94
121
  .option('-s, --server <url>', 'Server URL (can be specified multiple times)', collectOption)
95
122
  .option('--prefix <prefix>', 'API route prefix', '/api')
96
123
  .option('-r, --recursive', 'Scan subdirectories for procedures', false)
97
- .option('--pretty', 'Pretty-print JSON output', true)
98
- .option('--no-pretty', 'Minify JSON output')
124
+ .option('--pretty', 'Pretty-print output', true)
125
+ .option('--no-pretty', 'Minify output')
99
126
  .option('--validate', 'Validate generated spec for issues', true)
100
127
  .option('--no-validate', 'Skip validation')
101
128
  .option('-q, --quiet', 'Suppress output except errors', false)
@@ -146,12 +173,14 @@ function createGenerateCommand() {
146
173
  }
147
174
  // Add discovery warnings
148
175
  warnings.push(...discovery.warnings.map((w) => `${w.filePath}: ${w.message}`));
176
+ // Determine output format
177
+ const format = detectFormat(outputPath, options.format);
149
178
  // Write output
150
179
  await ensureDir(outputPath);
151
- const jsonContent = options.pretty !== false ? JSON.stringify(spec, null, 2) : JSON.stringify(spec);
152
- writeFileSync(outputPath, jsonContent, 'utf-8');
180
+ const content = serializeSpec(spec, format, options.pretty !== false);
181
+ writeFileSync(outputPath, content, 'utf-8');
153
182
  // Print success message
154
- printSuccess(outputPath, spec, warnings, options.quiet ?? false);
183
+ printSuccess(outputPath, spec, warnings, options.quiet ?? false, format);
155
184
  // Exit explicitly - dynamic imports may keep event loop running
156
185
  process.exit(0);
157
186
  }
@@ -171,28 +200,134 @@ function collectOption(value, previous = []) {
171
200
  return [...previous, value];
172
201
  }
173
202
  /**
174
- * Create the openapi:serve command (placeholder for future Swagger UI serving)
203
+ * Create the openapi:serve command
175
204
  */
176
205
  function createServeCommand() {
177
206
  return new Command('serve')
178
- .description('Start a local Swagger UI server (coming soon)')
179
- .option('-f, --file <file>', 'OpenAPI spec file', './openapi.json')
207
+ .description('Start a local Swagger UI server to preview OpenAPI documentation')
208
+ .option('-f, --file <file>', 'OpenAPI spec file (JSON or YAML)', './openapi.json')
180
209
  .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);
210
+ .option('--host <host>', 'Host to bind', 'localhost')
211
+ .option('-w, --watch', 'Watch for file changes and hot-reload', false)
212
+ .action(async (options) => {
213
+ const filePath = resolve(process.cwd(), options.file ?? './openapi.json');
214
+ const port = parseInt(options.port ?? '8080', 10);
215
+ const host = options.host ?? 'localhost';
216
+ const watchMode = options.watch ?? false;
217
+ // Validate port number
218
+ if (Number.isNaN(port) || port < 1 || port > 65535) {
219
+ console.error(pc.red(`Error: Invalid port number: ${options.port}. Must be between 1-65535.`));
220
+ process.exit(1);
221
+ }
222
+ // Verify spec file exists
223
+ if (!existsSync(filePath)) {
224
+ console.error(pc.red(`Error: OpenAPI spec file not found: ${filePath}`));
225
+ console.log(pc.dim('Run `velox openapi generate` first to create the spec.'));
226
+ process.exit(1);
227
+ }
228
+ // Load spec file
229
+ let spec;
230
+ try {
231
+ spec = loadSpecFile(filePath);
232
+ }
233
+ catch (error) {
234
+ console.error(pc.red(`Error: Failed to parse OpenAPI spec: ${error.message}`));
235
+ process.exit(1);
236
+ }
237
+ // Generate Swagger UI HTML
238
+ const title = spec.info?.title ?? 'API Documentation';
239
+ let htmlContent = generateSwaggerUIHtml({
240
+ specUrl: '/openapi.json',
241
+ title,
242
+ config: { tryItOutEnabled: true },
243
+ });
244
+ // Create HTTP server
245
+ const server = createServer((req, res) => {
246
+ // Handle CORS
247
+ res.setHeader('Access-Control-Allow-Origin', '*');
248
+ if (req.url === '/' || req.url === '/index.html') {
249
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
250
+ res.end(htmlContent);
251
+ }
252
+ else if (req.url === '/openapi.json') {
253
+ res.writeHead(200, { 'Content-Type': 'application/json' });
254
+ res.end(JSON.stringify(spec, null, 2));
255
+ }
256
+ else {
257
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
258
+ res.end('Not Found');
259
+ }
260
+ });
261
+ // Watch for file changes
262
+ let watcher;
263
+ let debounceTimer;
264
+ if (watchMode) {
265
+ watcher = watch(filePath, () => {
266
+ // Debounce rapid changes
267
+ if (debounceTimer) {
268
+ clearTimeout(debounceTimer);
269
+ }
270
+ debounceTimer = setTimeout(() => {
271
+ try {
272
+ spec = loadSpecFile(filePath);
273
+ htmlContent = generateSwaggerUIHtml({
274
+ specUrl: '/openapi.json',
275
+ title: spec.info?.title ?? 'API Documentation',
276
+ config: { tryItOutEnabled: true },
277
+ });
278
+ console.log(pc.green('✓') + pc.dim(' Spec reloaded'));
279
+ }
280
+ catch (error) {
281
+ console.error(pc.yellow('⚠') + pc.dim(` Failed to reload spec: ${error.message}`));
282
+ }
283
+ finally {
284
+ debounceTimer = undefined;
285
+ }
286
+ }, 100);
287
+ });
288
+ }
289
+ // Handle graceful shutdown
290
+ const shutdown = () => {
291
+ console.log();
292
+ console.log(pc.dim('Shutting down...'));
293
+ if (debounceTimer) {
294
+ clearTimeout(debounceTimer);
295
+ }
296
+ watcher?.close();
297
+ server.close(() => {
298
+ process.exit(0);
299
+ });
300
+ };
301
+ process.on('SIGINT', shutdown);
302
+ process.on('SIGTERM', shutdown);
303
+ // Start server
304
+ server.listen(port, host, () => {
305
+ console.log();
306
+ console.log(pc.green('✓') + pc.bold(' Swagger UI server started'));
307
+ console.log();
308
+ console.log(` ${pc.dim('URL:')} http://${host}:${port}`);
309
+ console.log(` ${pc.dim('Spec:')} ${filePath}`);
310
+ console.log(` ${pc.dim('Title:')} ${title}`);
311
+ if (watchMode) {
312
+ console.log(` ${pc.dim('Watch:')} ${pc.green('enabled')}`);
313
+ }
314
+ console.log();
315
+ console.log(pc.dim('Press Ctrl+C to stop'));
316
+ console.log();
317
+ });
194
318
  });
195
319
  }
320
+ /**
321
+ * Load and parse an OpenAPI spec file (JSON or YAML)
322
+ */
323
+ function loadSpecFile(filePath) {
324
+ const content = readFileSync(filePath, 'utf-8');
325
+ const ext = extname(filePath).toLowerCase();
326
+ if (ext === '.yaml' || ext === '.yml') {
327
+ return YAML.parse(content);
328
+ }
329
+ return JSON.parse(content);
330
+ }
196
331
  /**
197
332
  * Create the openapi command with subcommands
198
333
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veloxts/cli",
3
- "version": "0.6.84",
3
+ "version": "0.6.86",
4
4
  "description": "Developer tooling and CLI commands for VeloxTS framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -40,11 +40,12 @@
40
40
  "picocolors": "1.1.1",
41
41
  "pluralize": "8.0.0",
42
42
  "tsx": "4.21.0",
43
- "@veloxts/core": "0.6.84",
44
- "@veloxts/orm": "0.6.84",
45
- "@veloxts/auth": "0.6.84",
46
- "@veloxts/router": "0.6.84",
47
- "@veloxts/validation": "0.6.84"
43
+ "yaml": "2.8.0",
44
+ "@veloxts/orm": "0.6.86",
45
+ "@veloxts/router": "0.6.86",
46
+ "@veloxts/validation": "0.6.86",
47
+ "@veloxts/auth": "0.6.86",
48
+ "@veloxts/core": "0.6.86"
48
49
  },
49
50
  "peerDependencies": {
50
51
  "@prisma/client": ">=7.0.0"