katax-cli 0.1.0
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/README.md +0 -0
- package/dist/commands/add-endpoint.d.ts +6 -0
- package/dist/commands/add-endpoint.js +229 -0
- package/dist/commands/generate-crud.d.ts +5 -0
- package/dist/commands/generate-crud.js +282 -0
- package/dist/commands/info.d.ts +1 -0
- package/dist/commands/info.js +80 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.js +1315 -0
- package/dist/database/mongodb.ts +46 -0
- package/dist/database/mysql.ts +26 -0
- package/dist/database/postgresql.ts +52 -0
- package/dist/generators/controller-generator.d.ts +2 -0
- package/dist/generators/controller-generator.js +223 -0
- package/dist/generators/handler-generator.d.ts +2 -0
- package/dist/generators/handler-generator.js +84 -0
- package/dist/generators/route-generator.d.ts +2 -0
- package/dist/generators/route-generator.js +50 -0
- package/dist/generators/router-updater.d.ts +2 -0
- package/dist/generators/router-updater.js +48 -0
- package/dist/generators/validator-generator.d.ts +2 -0
- package/dist/generators/validator-generator.js +160 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +101 -0
- package/dist/types/index.d.ts +50 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/file-utils.d.ts +40 -0
- package/dist/utils/file-utils.js +89 -0
- package/dist/utils/logger.d.ts +10 -0
- package/dist/utils/logger.js +38 -0
- package/package.json +68 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { toPascalCase, toCamelCase } from '../utils/file-utils.js';
|
|
2
|
+
export function generateValidator(config) {
|
|
3
|
+
const { name, fields = [], addAsyncValidators } = config;
|
|
4
|
+
const pascalName = toPascalCase(name);
|
|
5
|
+
const camelName = toCamelCase(name);
|
|
6
|
+
let content = `import { k, kataxInfer } from 'katax-core';\n`;
|
|
7
|
+
content += `import type { ValidationResult } from '../../shared/api.utils.js';\n`;
|
|
8
|
+
if (addAsyncValidators) {
|
|
9
|
+
content += `import type { AsyncValidator } from 'katax-core';\n`;
|
|
10
|
+
content += `// import pool from '../../database/db.config.js'; // Uncomment if using database\n\n`;
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
content += '\n';
|
|
14
|
+
}
|
|
15
|
+
// Generate async validators if needed
|
|
16
|
+
if (addAsyncValidators) {
|
|
17
|
+
content += `// ==================== ASYNC VALIDATORS ====================\n\n`;
|
|
18
|
+
fields.forEach(field => {
|
|
19
|
+
if (field.asyncValidator) {
|
|
20
|
+
content += `/**\n * Check if ${field.name} is unique\n */\n`;
|
|
21
|
+
content += `export const ${field.name}UniqueValidator: AsyncValidator<string> = async (value, path) => {\n`;
|
|
22
|
+
content += ` console.log(\`[ASYNC] Checking ${field.name}: \${value}\`);\n`;
|
|
23
|
+
content += ` \n`;
|
|
24
|
+
content += ` // TODO: Implement database check\n`;
|
|
25
|
+
content += ` // const result = await pool.query(\n`;
|
|
26
|
+
content += ` // 'SELECT id FROM ${field.asyncValidator.table || 'table_name'} WHERE ${field.asyncValidator.column || field.name} = $1',\n`;
|
|
27
|
+
content += ` // [value]\n`;
|
|
28
|
+
content += ` // );\n`;
|
|
29
|
+
content += ` // \n`;
|
|
30
|
+
content += ` // if (result.rows.length > 0) {\n`;
|
|
31
|
+
content += ` // return [{ path, message: "This ${field.name} is already taken" }];\n`;
|
|
32
|
+
content += ` // }\n`;
|
|
33
|
+
content += ` \n`;
|
|
34
|
+
content += ` return [];\n`;
|
|
35
|
+
content += `};\n\n`;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// Generate schema
|
|
40
|
+
content += `// ==================== SCHEMAS ====================\n\n`;
|
|
41
|
+
content += `/**\n * Schema for ${name} ${config.method} request\n */\n`;
|
|
42
|
+
content += `export const ${camelName}Schema = k.object({\n`;
|
|
43
|
+
fields.forEach((field, index) => {
|
|
44
|
+
const isLast = index === fields.length - 1;
|
|
45
|
+
let fieldSchema = '';
|
|
46
|
+
// Base type
|
|
47
|
+
switch (field.type) {
|
|
48
|
+
case 'string':
|
|
49
|
+
fieldSchema = 'k.string()';
|
|
50
|
+
break;
|
|
51
|
+
case 'number':
|
|
52
|
+
fieldSchema = 'k.number()';
|
|
53
|
+
break;
|
|
54
|
+
case 'boolean':
|
|
55
|
+
fieldSchema = 'k.boolean()';
|
|
56
|
+
break;
|
|
57
|
+
case 'date':
|
|
58
|
+
fieldSchema = 'k.date()';
|
|
59
|
+
break;
|
|
60
|
+
case 'email':
|
|
61
|
+
fieldSchema = 'k.string().email()';
|
|
62
|
+
break;
|
|
63
|
+
case 'array':
|
|
64
|
+
fieldSchema = 'k.array(k.string())';
|
|
65
|
+
break;
|
|
66
|
+
case 'object':
|
|
67
|
+
fieldSchema = 'k.object({})';
|
|
68
|
+
break;
|
|
69
|
+
default:
|
|
70
|
+
fieldSchema = 'k.string()';
|
|
71
|
+
}
|
|
72
|
+
// Add rules
|
|
73
|
+
if (field.rules && field.rules.length > 0) {
|
|
74
|
+
field.rules.forEach(rule => {
|
|
75
|
+
switch (rule.type) {
|
|
76
|
+
case 'minLength':
|
|
77
|
+
fieldSchema += `\n .minLength(${rule.value}, '${rule.message || `Must be at least ${rule.value} characters`}')`;
|
|
78
|
+
break;
|
|
79
|
+
case 'maxLength':
|
|
80
|
+
fieldSchema += `\n .maxLength(${rule.value}, '${rule.message || `Must not exceed ${rule.value} characters`}')`;
|
|
81
|
+
break;
|
|
82
|
+
case 'min':
|
|
83
|
+
fieldSchema += `\n .min(${rule.value}, '${rule.message || `Must be at least ${rule.value}`}')`;
|
|
84
|
+
break;
|
|
85
|
+
case 'max':
|
|
86
|
+
fieldSchema += `\n .max(${rule.value}, '${rule.message || `Must not exceed ${rule.value}`}')`;
|
|
87
|
+
break;
|
|
88
|
+
case 'email':
|
|
89
|
+
fieldSchema += `\n .email('${rule.message || 'Must be a valid email'}')`;
|
|
90
|
+
break;
|
|
91
|
+
case 'regex':
|
|
92
|
+
fieldSchema += `\n .regex(${rule.value}, '${rule.message || 'Invalid format'}')`;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
// Add async validator
|
|
98
|
+
if (field.asyncValidator && addAsyncValidators) {
|
|
99
|
+
fieldSchema += `\n .asyncRefine(${field.name}UniqueValidator)`;
|
|
100
|
+
}
|
|
101
|
+
// Add optional
|
|
102
|
+
if (!field.required) {
|
|
103
|
+
fieldSchema += `\n .optional()`;
|
|
104
|
+
}
|
|
105
|
+
content += ` ${field.name}: ${fieldSchema}${isLast ? '' : ','}\n`;
|
|
106
|
+
});
|
|
107
|
+
content += `});\n\n`;
|
|
108
|
+
// Generate type
|
|
109
|
+
content += `/**\n * Inferred TypeScript type from schema\n */\n`;
|
|
110
|
+
content += `export type ${pascalName}Data = kataxInfer<typeof ${camelName}Schema>;\n\n`;
|
|
111
|
+
// Generate ID schema for GET/DELETE operations
|
|
112
|
+
if (config.method === 'GET' || config.method === 'DELETE' || config.method === 'PUT' || config.method === 'PATCH') {
|
|
113
|
+
content += `/**\n * Schema for ${name} ID validation\n */\n`;
|
|
114
|
+
content += `export const ${camelName}IdSchema = k.string()\n`;
|
|
115
|
+
content += ` .minLength(1, 'ID is required')\n`;
|
|
116
|
+
content += ` .regex(/^[0-9a-fA-F-]{36}$|^\\d+$/, 'ID must be a valid UUID or integer');\n\n`;
|
|
117
|
+
content += `export type ${pascalName}IdType = kataxInfer<typeof ${camelName}IdSchema>;\n\n`;
|
|
118
|
+
}
|
|
119
|
+
// Generate validation functions (ValidationResult is imported from api.utils)
|
|
120
|
+
content += `/**\n * Validate ${name} data\n */\n`;
|
|
121
|
+
content += `export async function validate${pascalName}(data: unknown): Promise<ValidationResult<${pascalName}Data>> {\n`;
|
|
122
|
+
content += ` const result = ${addAsyncValidators ? 'await ' : ''}${camelName}Schema.safeParse${addAsyncValidators ? 'Async' : ''}(data);\n\n`;
|
|
123
|
+
content += ` if (!result.success) {\n`;
|
|
124
|
+
content += ` const errors = result.issues.map(issue => ({\n`;
|
|
125
|
+
content += ` field: issue.path.join('.'),\n`;
|
|
126
|
+
content += ` message: issue.message\n`;
|
|
127
|
+
content += ` }));\n\n`;
|
|
128
|
+
content += ` return {\n`;
|
|
129
|
+
content += ` isValid: false,\n`;
|
|
130
|
+
content += ` errors\n`;
|
|
131
|
+
content += ` };\n`;
|
|
132
|
+
content += ` }\n\n`;
|
|
133
|
+
content += ` return {\n`;
|
|
134
|
+
content += ` isValid: true,\n`;
|
|
135
|
+
content += ` data: result.data\n`;
|
|
136
|
+
content += ` };\n`;
|
|
137
|
+
content += `}\n`;
|
|
138
|
+
// Generate ID validation function for GET/DELETE
|
|
139
|
+
if (config.method === 'GET' || config.method === 'DELETE' || config.method === 'PUT' || config.method === 'PATCH') {
|
|
140
|
+
content += `\n/**\n * Validate ${name} ID\n */\n`;
|
|
141
|
+
content += `export async function validate${pascalName}Id(id: string): Promise<ValidationResult<${pascalName}IdType>> {\n`;
|
|
142
|
+
content += ` const result = ${camelName}IdSchema.safeParse(id);\n\n`;
|
|
143
|
+
content += ` if (!result.success) {\n`;
|
|
144
|
+
content += ` const errors = result.issues.map(issue => ({\n`;
|
|
145
|
+
content += ` field: issue.path.join('.') || 'id',\n`;
|
|
146
|
+
content += ` message: issue.message\n`;
|
|
147
|
+
content += ` }));\n\n`;
|
|
148
|
+
content += ` return {\n`;
|
|
149
|
+
content += ` isValid: false,\n`;
|
|
150
|
+
content += ` errors\n`;
|
|
151
|
+
content += ` };\n`;
|
|
152
|
+
content += ` }\n\n`;
|
|
153
|
+
content += ` return {\n`;
|
|
154
|
+
content += ` isValid: true,\n`;
|
|
155
|
+
content += ` data: result.data\n`;
|
|
156
|
+
content += ` };\n`;
|
|
157
|
+
content += `}\n`;
|
|
158
|
+
}
|
|
159
|
+
return content;
|
|
160
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Katax CLI - API Generator with TypeScript and katax-core validation
|
|
4
|
+
*
|
|
5
|
+
* A CLI tool for generating Express REST APIs with TypeScript,
|
|
6
|
+
* integrated with katax-core for robust schema validation.
|
|
7
|
+
*/
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import { readFileSync } from 'fs';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import { dirname, join } from 'path';
|
|
13
|
+
import { initCommand } from './commands/init.js';
|
|
14
|
+
import { addEndpointCommand } from './commands/add-endpoint.js';
|
|
15
|
+
import { generateCrudCommand } from './commands/generate-crud.js';
|
|
16
|
+
import { infoCommand } from './commands/info.js';
|
|
17
|
+
import { setVerbose, setColorMode } from './utils/logger.js';
|
|
18
|
+
// Get version from package.json
|
|
19
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
20
|
+
const __dirname = dirname(__filename);
|
|
21
|
+
const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
|
|
22
|
+
const version = packageJson.version;
|
|
23
|
+
const program = new Command();
|
|
24
|
+
program
|
|
25
|
+
.name('katax')
|
|
26
|
+
.description(chalk.blue('🚀 Generate Express APIs with TypeScript and katax-core validation'))
|
|
27
|
+
.version(version, '-v, --version', 'Output the current version');
|
|
28
|
+
// Init command - Initialize new API project
|
|
29
|
+
program
|
|
30
|
+
.command('init [project-name]')
|
|
31
|
+
.description('Initialize a new Express API project with TypeScript')
|
|
32
|
+
.option('-f, --force', 'Overwrite existing project')
|
|
33
|
+
.action(initCommand);
|
|
34
|
+
// Add command - Add resources to project
|
|
35
|
+
const addCommand = program
|
|
36
|
+
.command('add')
|
|
37
|
+
.description('Add resources to your project');
|
|
38
|
+
addCommand
|
|
39
|
+
.command('endpoint <name>')
|
|
40
|
+
.description('Add a new endpoint with validation')
|
|
41
|
+
.option('-m, --method <method>', 'HTTP method (GET, POST, PUT, DELETE)', 'POST')
|
|
42
|
+
.option('-p, --path <path>', 'Route path')
|
|
43
|
+
.action(addEndpointCommand);
|
|
44
|
+
// Generate command - Generate complete resources
|
|
45
|
+
const generateCommand = program
|
|
46
|
+
.command('generate')
|
|
47
|
+
.aliases(['gen', 'g'])
|
|
48
|
+
.description('Generate complete resources');
|
|
49
|
+
generateCommand
|
|
50
|
+
.command('crud <resource-name>')
|
|
51
|
+
.description('Generate a complete CRUD resource')
|
|
52
|
+
.option('--no-auth', 'Skip authentication middleware')
|
|
53
|
+
.action(generateCrudCommand);
|
|
54
|
+
// Info command - Show project structure
|
|
55
|
+
program
|
|
56
|
+
.command('info')
|
|
57
|
+
.aliases(['status', 'ls'])
|
|
58
|
+
.description('Show current project structure and routes')
|
|
59
|
+
.action(infoCommand);
|
|
60
|
+
// Global options
|
|
61
|
+
program
|
|
62
|
+
.option('--no-color', 'Disable colored output')
|
|
63
|
+
.option('--verbose', 'Enable verbose logging');
|
|
64
|
+
// Parse global options
|
|
65
|
+
program.hook('preAction', (thisCommand) => {
|
|
66
|
+
const opts = thisCommand.optsWithGlobals();
|
|
67
|
+
if (opts.verbose)
|
|
68
|
+
setVerbose(true);
|
|
69
|
+
if (opts.color === false)
|
|
70
|
+
setColorMode(false);
|
|
71
|
+
});
|
|
72
|
+
// Show help after error and suggestions
|
|
73
|
+
program.showHelpAfterError('(add --help for additional information)');
|
|
74
|
+
program.showSuggestionAfterError(true);
|
|
75
|
+
// Add examples to help
|
|
76
|
+
program.addHelpText('after', `
|
|
77
|
+
${chalk.bold('Examples:')}
|
|
78
|
+
${chalk.gray('# Initialize a new API project')}
|
|
79
|
+
$ katax init my-api
|
|
80
|
+
|
|
81
|
+
${chalk.gray('# Add a single endpoint')}
|
|
82
|
+
$ katax add endpoint users
|
|
83
|
+
|
|
84
|
+
${chalk.gray('# Generate a complete CRUD resource')}
|
|
85
|
+
$ katax generate crud products
|
|
86
|
+
|
|
87
|
+
${chalk.gray('# View project structure')}
|
|
88
|
+
$ katax info
|
|
89
|
+
|
|
90
|
+
${chalk.gray('# Initialize with options')}
|
|
91
|
+
$ katax init my-api --force
|
|
92
|
+
|
|
93
|
+
${chalk.bold('Documentation:')}
|
|
94
|
+
${chalk.cyan('https://github.com/LOPIN6FARRIER/katax-cli#readme')}
|
|
95
|
+
`);
|
|
96
|
+
// Parse command line arguments
|
|
97
|
+
program.parse(process.argv);
|
|
98
|
+
// Show help if no command provided
|
|
99
|
+
if (!process.argv.slice(2).length) {
|
|
100
|
+
program.outputHelp();
|
|
101
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface ProjectConfig {
|
|
2
|
+
name: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
type: 'rest-api' | 'graphql';
|
|
5
|
+
typescript: boolean;
|
|
6
|
+
database?: 'postgresql' | 'mysql' | 'mongodb' | 'none';
|
|
7
|
+
authentication?: 'jwt' | 'none';
|
|
8
|
+
validation: 'katax-core' | 'none';
|
|
9
|
+
orm?: 'none' | 'prisma' | 'typeorm';
|
|
10
|
+
port: number;
|
|
11
|
+
dbConfig?: {
|
|
12
|
+
host?: string;
|
|
13
|
+
port?: string;
|
|
14
|
+
user?: string;
|
|
15
|
+
password?: string;
|
|
16
|
+
database?: string;
|
|
17
|
+
useAuth?: boolean;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export interface EndpointConfig {
|
|
21
|
+
name: string;
|
|
22
|
+
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
23
|
+
path: string;
|
|
24
|
+
addValidation: boolean;
|
|
25
|
+
fields?: FieldConfig[];
|
|
26
|
+
addAsyncValidators: boolean;
|
|
27
|
+
dbOperations?: ('create' | 'read' | 'update' | 'delete')[];
|
|
28
|
+
}
|
|
29
|
+
export interface FieldConfig {
|
|
30
|
+
name: string;
|
|
31
|
+
type: 'string' | 'number' | 'boolean' | 'date' | 'email' | 'array' | 'object';
|
|
32
|
+
required: boolean;
|
|
33
|
+
rules?: ValidationRule[];
|
|
34
|
+
asyncValidator?: {
|
|
35
|
+
type: 'unique' | 'exists' | 'custom';
|
|
36
|
+
table?: string;
|
|
37
|
+
column?: string;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export interface ValidationRule {
|
|
41
|
+
type: 'minLength' | 'maxLength' | 'min' | 'max' | 'email' | 'regex' | 'oneOf' | 'custom';
|
|
42
|
+
value?: any;
|
|
43
|
+
message?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface CRUDConfig {
|
|
46
|
+
resourceName: string;
|
|
47
|
+
tableName?: string;
|
|
48
|
+
fields: FieldConfig[];
|
|
49
|
+
addAuth: boolean;
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the templates directory path
|
|
3
|
+
*/
|
|
4
|
+
export declare function getTemplatesDir(): string;
|
|
5
|
+
/**
|
|
6
|
+
* Render an EJS template
|
|
7
|
+
*/
|
|
8
|
+
export declare function renderTemplate(templatePath: string, data: any): Promise<string>;
|
|
9
|
+
/**
|
|
10
|
+
* Copy template file to destination
|
|
11
|
+
*/
|
|
12
|
+
export declare function copyTemplate(templatePath: string, destinationPath: string, data?: any): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Write a file with content
|
|
15
|
+
*/
|
|
16
|
+
export declare function writeFile(filePath: string, content: string): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Check if directory exists
|
|
19
|
+
*/
|
|
20
|
+
export declare function directoryExists(dirPath: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Check if file exists
|
|
23
|
+
*/
|
|
24
|
+
export declare function fileExists(filePath: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Create directory if it doesn't exist
|
|
27
|
+
*/
|
|
28
|
+
export declare function ensureDir(dirPath: string): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Convert string to PascalCase
|
|
31
|
+
*/
|
|
32
|
+
export declare function toPascalCase(str: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Convert string to camelCase
|
|
35
|
+
*/
|
|
36
|
+
export declare function toCamelCase(str: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* Convert string to kebab-case
|
|
39
|
+
*/
|
|
40
|
+
export declare function toKebabCase(str: string): string;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import ejs from 'ejs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname } from 'path';
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
8
|
+
/**
|
|
9
|
+
* Get the templates directory path
|
|
10
|
+
*/
|
|
11
|
+
export function getTemplatesDir() {
|
|
12
|
+
// In development: ../../templates
|
|
13
|
+
// In production: ../templates (dist folder)
|
|
14
|
+
const devPath = path.join(__dirname, '..', '..', 'templates');
|
|
15
|
+
const prodPath = path.join(__dirname, '..', 'templates');
|
|
16
|
+
return fs.existsSync(devPath) ? devPath : prodPath;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Render an EJS template
|
|
20
|
+
*/
|
|
21
|
+
export async function renderTemplate(templatePath, data) {
|
|
22
|
+
const fullPath = path.join(getTemplatesDir(), templatePath);
|
|
23
|
+
const templateContent = await fs.readFile(fullPath, 'utf-8');
|
|
24
|
+
return ejs.render(templateContent, data);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Copy template file to destination
|
|
28
|
+
*/
|
|
29
|
+
export async function copyTemplate(templatePath, destinationPath, data) {
|
|
30
|
+
const sourcePath = path.join(getTemplatesDir(), templatePath);
|
|
31
|
+
await fs.ensureDir(path.dirname(destinationPath));
|
|
32
|
+
if (data) {
|
|
33
|
+
const rendered = await renderTemplate(templatePath, data);
|
|
34
|
+
await fs.writeFile(destinationPath, rendered, 'utf-8');
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
await fs.copy(sourcePath, destinationPath);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Write a file with content
|
|
42
|
+
*/
|
|
43
|
+
export async function writeFile(filePath, content) {
|
|
44
|
+
await fs.ensureDir(path.dirname(filePath));
|
|
45
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Check if directory exists
|
|
49
|
+
*/
|
|
50
|
+
export function directoryExists(dirPath) {
|
|
51
|
+
return fs.existsSync(dirPath);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Check if file exists
|
|
55
|
+
*/
|
|
56
|
+
export function fileExists(filePath) {
|
|
57
|
+
return fs.existsSync(filePath) && fs.statSync(filePath).isFile();
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Create directory if it doesn't exist
|
|
61
|
+
*/
|
|
62
|
+
export async function ensureDir(dirPath) {
|
|
63
|
+
await fs.ensureDir(dirPath);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Convert string to PascalCase
|
|
67
|
+
*/
|
|
68
|
+
export function toPascalCase(str) {
|
|
69
|
+
return str
|
|
70
|
+
.split(/[-_\s]+/)
|
|
71
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
72
|
+
.join('');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Convert string to camelCase
|
|
76
|
+
*/
|
|
77
|
+
export function toCamelCase(str) {
|
|
78
|
+
const pascal = toPascalCase(str);
|
|
79
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Convert string to kebab-case
|
|
83
|
+
*/
|
|
84
|
+
export function toKebabCase(str) {
|
|
85
|
+
return str
|
|
86
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
87
|
+
.replace(/[\s_]+/g, '-')
|
|
88
|
+
.toLowerCase();
|
|
89
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function setVerbose(enabled: boolean): void;
|
|
2
|
+
export declare function setColorMode(enabled: boolean): void;
|
|
3
|
+
export declare function success(message: string): void;
|
|
4
|
+
export declare function error(message: string): void;
|
|
5
|
+
export declare function warning(message: string): void;
|
|
6
|
+
export declare function info(message: string): void;
|
|
7
|
+
export declare function verbose(message: string): void;
|
|
8
|
+
export declare function gray(message: string): void;
|
|
9
|
+
export declare function title(message: string): void;
|
|
10
|
+
export declare function code(message: string): void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
let verboseMode = false;
|
|
3
|
+
let colorMode = true;
|
|
4
|
+
export function setVerbose(enabled) {
|
|
5
|
+
verboseMode = enabled;
|
|
6
|
+
}
|
|
7
|
+
export function setColorMode(enabled) {
|
|
8
|
+
colorMode = enabled;
|
|
9
|
+
}
|
|
10
|
+
function applyColor(color, text) {
|
|
11
|
+
return colorMode ? color(text) : text;
|
|
12
|
+
}
|
|
13
|
+
export function success(message) {
|
|
14
|
+
console.log(applyColor(chalk.green, `✅ ${message}`));
|
|
15
|
+
}
|
|
16
|
+
export function error(message) {
|
|
17
|
+
console.log(applyColor(chalk.red, `❌ ${message}`));
|
|
18
|
+
}
|
|
19
|
+
export function warning(message) {
|
|
20
|
+
console.log(applyColor(chalk.yellow, `⚠️ ${message}`));
|
|
21
|
+
}
|
|
22
|
+
export function info(message) {
|
|
23
|
+
console.log(applyColor(chalk.blue, `ℹ️ ${message}`));
|
|
24
|
+
}
|
|
25
|
+
export function verbose(message) {
|
|
26
|
+
if (verboseMode) {
|
|
27
|
+
console.log(applyColor(chalk.gray, `[VERBOSE] ${message}`));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function gray(message) {
|
|
31
|
+
console.log(applyColor(chalk.gray, message));
|
|
32
|
+
}
|
|
33
|
+
export function title(message) {
|
|
34
|
+
console.log(applyColor(chalk.bold.cyan, `\n${message}\n`));
|
|
35
|
+
}
|
|
36
|
+
export function code(message) {
|
|
37
|
+
console.log(applyColor(chalk.bgBlack.white, ` ${message} `));
|
|
38
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "katax-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI tool to generate Express APIs with TypeScript and katax-core validation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"katax": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"templates",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18.0.0"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"dev": "tsx watch src/index.ts",
|
|
21
|
+
"build": "tsc && npm run copy-templates",
|
|
22
|
+
"copy-templates": "copyfiles -u 1 \"templates/**/*\" dist",
|
|
23
|
+
"start": "node dist/index.js",
|
|
24
|
+
"clean": "rimraf dist",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"api",
|
|
29
|
+
"generator",
|
|
30
|
+
"express",
|
|
31
|
+
"typescript",
|
|
32
|
+
"validation",
|
|
33
|
+
"katax-core",
|
|
34
|
+
"cli",
|
|
35
|
+
"rest-api",
|
|
36
|
+
"crud"
|
|
37
|
+
],
|
|
38
|
+
"author": "Vinicio Esparza",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/LOPIN6FARRIER/katax-cli.git"
|
|
43
|
+
},
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/LOPIN6FARRIER/katax-cli/issues"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://github.com/LOPIN6FARRIER/katax-cli#readme",
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"chalk": "^5.3.0",
|
|
50
|
+
"commander": "^12.1.0",
|
|
51
|
+
"ejs": "^3.1.10",
|
|
52
|
+
"execa": "^9.5.2",
|
|
53
|
+
"fs-extra": "^11.2.0",
|
|
54
|
+
"inquirer": "^12.3.0",
|
|
55
|
+
"katax-core": "^1.1.0",
|
|
56
|
+
"ora": "^8.1.1"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/ejs": "^3.1.5",
|
|
60
|
+
"@types/fs-extra": "^11.0.4",
|
|
61
|
+
"@types/inquirer": "^9.0.7",
|
|
62
|
+
"@types/node": "^22.10.5",
|
|
63
|
+
"copyfiles": "^2.4.1",
|
|
64
|
+
"rimraf": "^6.0.1",
|
|
65
|
+
"tsx": "^4.19.2",
|
|
66
|
+
"typescript": "^5.7.2"
|
|
67
|
+
}
|
|
68
|
+
}
|