create-phoenixjs 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.
Files changed (49) hide show
  1. package/index.ts +196 -0
  2. package/package.json +31 -0
  3. package/template/README.md +62 -0
  4. package/template/app/controllers/ExampleController.ts +61 -0
  5. package/template/artisan +2 -0
  6. package/template/bootstrap/app.ts +44 -0
  7. package/template/bunfig.toml +7 -0
  8. package/template/config/database.ts +25 -0
  9. package/template/config/plugins.ts +7 -0
  10. package/template/config/security.ts +158 -0
  11. package/template/framework/cli/Command.ts +17 -0
  12. package/template/framework/cli/ConsoleApplication.ts +55 -0
  13. package/template/framework/cli/artisan.ts +16 -0
  14. package/template/framework/cli/commands/MakeControllerCommand.ts +41 -0
  15. package/template/framework/cli/commands/MakeMiddlewareCommand.ts +41 -0
  16. package/template/framework/cli/commands/MakeModelCommand.ts +36 -0
  17. package/template/framework/cli/commands/MakeValidatorCommand.ts +42 -0
  18. package/template/framework/controller/Controller.ts +222 -0
  19. package/template/framework/core/Application.ts +208 -0
  20. package/template/framework/core/Container.ts +100 -0
  21. package/template/framework/core/Kernel.ts +297 -0
  22. package/template/framework/database/DatabaseAdapter.ts +18 -0
  23. package/template/framework/database/PrismaAdapter.ts +65 -0
  24. package/template/framework/database/SqlAdapter.ts +117 -0
  25. package/template/framework/gateway/Gateway.ts +109 -0
  26. package/template/framework/gateway/GatewayManager.ts +150 -0
  27. package/template/framework/gateway/WebSocketAdapter.ts +159 -0
  28. package/template/framework/gateway/WebSocketGateway.ts +182 -0
  29. package/template/framework/http/Request.ts +608 -0
  30. package/template/framework/http/Response.ts +525 -0
  31. package/template/framework/http/Server.ts +161 -0
  32. package/template/framework/http/UploadedFile.ts +145 -0
  33. package/template/framework/middleware/Middleware.ts +50 -0
  34. package/template/framework/middleware/Pipeline.ts +89 -0
  35. package/template/framework/plugin/Plugin.ts +26 -0
  36. package/template/framework/plugin/PluginManager.ts +61 -0
  37. package/template/framework/routing/RouteRegistry.ts +185 -0
  38. package/template/framework/routing/Router.ts +280 -0
  39. package/template/framework/security/CorsMiddleware.ts +151 -0
  40. package/template/framework/security/CsrfMiddleware.ts +121 -0
  41. package/template/framework/security/HelmetMiddleware.ts +138 -0
  42. package/template/framework/security/InputSanitizerMiddleware.ts +134 -0
  43. package/template/framework/security/RateLimiterMiddleware.ts +189 -0
  44. package/template/framework/security/SecurityManager.ts +128 -0
  45. package/template/framework/validation/Validator.ts +482 -0
  46. package/template/package.json +24 -0
  47. package/template/routes/api.ts +56 -0
  48. package/template/server.ts +29 -0
  49. package/template/tsconfig.json +49 -0
package/index.ts ADDED
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { existsSync, mkdirSync, readdirSync, statSync, readFileSync, writeFileSync, copyFileSync, chmodSync } from 'fs';
4
+ import { join, dirname, basename } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+
10
+ // Colors for terminal output
11
+ const colors = {
12
+ reset: '\x1b[0m',
13
+ bold: '\x1b[1m',
14
+ green: '\x1b[32m',
15
+ cyan: '\x1b[36m',
16
+ yellow: '\x1b[33m',
17
+ red: '\x1b[31m',
18
+ dim: '\x1b[2m',
19
+ };
20
+
21
+ function log(message: string): void {
22
+ console.log(message);
23
+ }
24
+
25
+ function success(message: string): void {
26
+ console.log(`${colors.green}✓${colors.reset} ${message}`);
27
+ }
28
+
29
+ function error(message: string): void {
30
+ console.error(`${colors.red}✗${colors.reset} ${message}`);
31
+ }
32
+
33
+ function info(message: string): void {
34
+ console.log(`${colors.cyan}→${colors.reset} ${message}`);
35
+ }
36
+
37
+ function printBanner(): void {
38
+ console.log(`
39
+ ${colors.cyan}${colors.bold}
40
+ P H O E N I X J S
41
+ ${colors.reset}${colors.dim} Create a new PhoenixJS project${colors.reset}
42
+ `);
43
+ }
44
+
45
+ function copyDir(src: string, dest: string): void {
46
+ mkdirSync(dest, { recursive: true });
47
+
48
+ const entries = readdirSync(src);
49
+
50
+ for (const entry of entries) {
51
+ const srcPath = join(src, entry);
52
+ const destPath = join(dest, entry);
53
+
54
+ const stat = statSync(srcPath);
55
+
56
+ if (stat.isDirectory()) {
57
+ copyDir(srcPath, destPath);
58
+ } else {
59
+ copyFileSync(srcPath, destPath);
60
+ }
61
+ }
62
+ }
63
+
64
+ function updatePackageJson(projectPath: string, projectName: string): void {
65
+ const packageJsonPath = join(projectPath, 'package.json');
66
+
67
+ if (existsSync(packageJsonPath)) {
68
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
69
+ packageJson.name = projectName;
70
+ writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
71
+ }
72
+ }
73
+
74
+ function makeExecutable(projectPath: string): void {
75
+ const artisanPath = join(projectPath, 'artisan');
76
+ if (existsSync(artisanPath)) {
77
+ chmodSync(artisanPath, 0o755);
78
+ }
79
+ }
80
+
81
+ async function main(): Promise<void> {
82
+ const args = process.argv.slice(2);
83
+
84
+ // Handle help flag
85
+ if (args.includes('--help') || args.includes('-h')) {
86
+ printBanner();
87
+ console.log(`${colors.bold}Usage:${colors.reset}`);
88
+ console.log(` bunx create-phoenixjs <project-name>`);
89
+ console.log(` npx create-phoenixjs <project-name>`);
90
+ console.log();
91
+ console.log(`${colors.bold}Examples:${colors.reset}`);
92
+ console.log(` bunx create-phoenixjs my-api`);
93
+ console.log(` bunx create-phoenixjs ./my-project`);
94
+ console.log();
95
+ console.log(`${colors.bold}Options:${colors.reset}`);
96
+ console.log(` -h, --help Show this help message`);
97
+ console.log(` -v, --version Show version number`);
98
+ process.exit(0);
99
+ }
100
+
101
+ // Handle version flag
102
+ if (args.includes('--version') || args.includes('-v')) {
103
+ console.log('0.1.0');
104
+ process.exit(0);
105
+ }
106
+
107
+ printBanner();
108
+
109
+ // Get project name
110
+ const projectName = args[0];
111
+
112
+ if (!projectName) {
113
+ error('Please specify a project name:');
114
+ console.log();
115
+ console.log(` ${colors.cyan}bunx create-phoenixjs${colors.reset} ${colors.green}<project-name>${colors.reset}`);
116
+ console.log();
117
+ console.log('For example:');
118
+ console.log(` ${colors.cyan}bunx create-phoenixjs${colors.reset} ${colors.green}my-api${colors.reset}`);
119
+ process.exit(1);
120
+ }
121
+
122
+ const projectPath = join(process.cwd(), projectName);
123
+ const templatePath = join(__dirname, 'template');
124
+
125
+ // Check if directory already exists
126
+ if (existsSync(projectPath)) {
127
+ const files = readdirSync(projectPath);
128
+ if (files.length > 0) {
129
+ error(`Directory "${projectName}" already exists and is not empty.`);
130
+ process.exit(1);
131
+ }
132
+ }
133
+
134
+ // Check if template exists
135
+ if (!existsSync(templatePath)) {
136
+ error('Template files not found. Please reinstall create-phoenixjs.');
137
+ process.exit(1);
138
+ }
139
+
140
+ log('');
141
+ info(`Creating a new PhoenixJS project in ${colors.cyan}${projectPath}${colors.reset}`);
142
+ log('');
143
+
144
+ // Copy template files
145
+ try {
146
+ copyDir(templatePath, projectPath);
147
+ success('Copied project files');
148
+ } catch (err) {
149
+ error(`Failed to copy template files: ${err}`);
150
+ process.exit(1);
151
+ }
152
+
153
+ // Update package.json with project name
154
+ try {
155
+ updatePackageJson(projectPath, basename(projectName));
156
+ success('Updated package.json');
157
+ } catch (err) {
158
+ error(`Failed to update package.json: ${err}`);
159
+ }
160
+
161
+ // Make artisan executable
162
+ try {
163
+ makeExecutable(projectPath);
164
+ success('Made artisan executable');
165
+ } catch (err) {
166
+ // Non-critical error
167
+ }
168
+
169
+ // Print success message
170
+ log('');
171
+ console.log(`${colors.green}${colors.bold}Success!${colors.reset} Created ${colors.cyan}${projectName}${colors.reset}`);
172
+ log('');
173
+ console.log('Inside that directory, you can run:');
174
+ log('');
175
+ console.log(` ${colors.cyan}bun install${colors.reset}`);
176
+ console.log(' Install dependencies');
177
+ log('');
178
+ console.log(` ${colors.cyan}bun run dev${colors.reset}`);
179
+ console.log(' Start the development server');
180
+ log('');
181
+ console.log(` ${colors.cyan}bun artisan make:controller UserController${colors.reset}`);
182
+ console.log(' Generate a new controller');
183
+ log('');
184
+ console.log('Get started by running:');
185
+ log('');
186
+ console.log(` ${colors.cyan}cd ${projectName}${colors.reset}`);
187
+ console.log(` ${colors.cyan}bun install${colors.reset}`);
188
+ console.log(` ${colors.cyan}bun run dev${colors.reset}`);
189
+ log('');
190
+ console.log(`${colors.dim}Happy coding! 🚀${colors.reset}`);
191
+ }
192
+
193
+ main().catch((err) => {
194
+ error(`An unexpected error occurred: ${err}`);
195
+ process.exit(1);
196
+ });
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "create-phoenixjs",
3
+ "version": "0.1.0",
4
+ "description": "Create a new PhoenixJS project - A TypeScript framework inspired by Laravel, powered by Bun",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-phoenixjs": "./index.ts"
8
+ },
9
+ "files": [
10
+ "index.ts",
11
+ "template"
12
+ ],
13
+ "keywords": [
14
+ "phoenixjs",
15
+ "create",
16
+ "scaffold",
17
+ "bun",
18
+ "typescript",
19
+ "api",
20
+ "framework"
21
+ ],
22
+ "author": "",
23
+ "license": "MIT",
24
+ "engines": {
25
+ "bun": ">=1.0.0"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": ""
30
+ }
31
+ }
@@ -0,0 +1,62 @@
1
+ # PhoenixJS Project
2
+
3
+ A TypeScript API project built with [PhoenixJS](https://github.com/your-username/phoenixjs), powered by Bun.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Install dependencies
9
+ bun install
10
+
11
+ # Start development server (with hot reload)
12
+ bun run dev
13
+ ```
14
+
15
+ Your API is running at `http://localhost:4000`
16
+
17
+ ## Available Endpoints
18
+
19
+ | Method | Path | Description |
20
+ |--------|------|-------------|
21
+ | GET | `/api/health` | Health check |
22
+ | GET | `/api/hello` | Welcome message |
23
+ | GET | `/api/users/:id` | Get user by ID |
24
+ | POST | `/api/users` | Create user |
25
+
26
+ ## CLI Commands
27
+
28
+ ```bash
29
+ # Generate a new controller
30
+ bun artisan make:controller UserController
31
+
32
+ # Generate a validator
33
+ bun artisan make:validator CreateUserValidator
34
+
35
+ # Generate a model
36
+ bun artisan make:model User
37
+
38
+ # Generate middleware
39
+ bun artisan make:middleware AuthMiddleware
40
+ ```
41
+
42
+ ## Project Structure
43
+
44
+ ```
45
+ ├── app/
46
+ │ ├── controllers/ # HTTP controllers
47
+ │ ├── middlewares/ # Custom middleware
48
+ │ ├── validators/ # Request validators
49
+ │ ├── models/ # Data models
50
+ │ └── gateways/ # WebSocket gateways
51
+ ├── routes/
52
+ │ └── api.ts # API route definitions
53
+ ├── config/ # Configuration files
54
+ ├── framework/ # Core framework (don't modify)
55
+ ├── bootstrap/app.ts # Application bootstrap
56
+ └── server.ts # Entry point
57
+ ```
58
+
59
+ ## Learn More
60
+
61
+ - [Getting Started Guide](https://github.com/your-username/phoenixjs/blob/main/docs/README.md)
62
+ - [Full Documentation](https://github.com/your-username/phoenixjs/blob/main/README.md)
@@ -0,0 +1,61 @@
1
+ import { Controller } from '@framework/controller/Controller';
2
+
3
+ /**
4
+ * Example Controller
5
+ *
6
+ * This is a sample controller to get you started.
7
+ * Generate new controllers using: bun artisan make:controller <name>
8
+ */
9
+ export class ExampleController extends Controller {
10
+ /**
11
+ * Display a listing of the resource.
12
+ */
13
+ async index() {
14
+ return this.json({
15
+ message: 'Welcome to PhoenixJS!',
16
+ items: [],
17
+ });
18
+ }
19
+
20
+ /**
21
+ * Display the specified resource.
22
+ */
23
+ async show() {
24
+ const id = this.param('id');
25
+ return this.json({
26
+ id,
27
+ message: `Showing resource ${id}`,
28
+ });
29
+ }
30
+
31
+ /**
32
+ * Store a newly created resource.
33
+ */
34
+ async store() {
35
+ const data = await this.request.json();
36
+ return this.json({
37
+ message: 'Resource created successfully',
38
+ data,
39
+ }, 201);
40
+ }
41
+
42
+ /**
43
+ * Update the specified resource.
44
+ */
45
+ async update() {
46
+ const id = this.param('id');
47
+ const data = await this.request.json();
48
+ return this.json({
49
+ message: `Resource ${id} updated`,
50
+ data,
51
+ });
52
+ }
53
+
54
+ /**
55
+ * Remove the specified resource.
56
+ */
57
+ async destroy() {
58
+ const id = this.param('id');
59
+ return this.noContent();
60
+ }
61
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ import './framework/cli/artisan';
@@ -0,0 +1,44 @@
1
+ /**
2
+ * PhoenixJS - Application Bootstrap
3
+ *
4
+ * This file creates and configures the application instance.
5
+ */
6
+
7
+ import { Application } from '@framework/core/Application';
8
+ import { Kernel } from '@framework/core/Kernel';
9
+ import { registerRoutes } from '@/routes/api';
10
+
11
+ /**
12
+ * Create and configure the application
13
+ */
14
+ export function createApplication(): Application {
15
+ const app = Application.create();
16
+
17
+ // Configure application
18
+ app.configure({
19
+ name: 'PhoenixJS',
20
+ env: (process.env.NODE_ENV as 'development' | 'production' | 'testing') ?? 'development',
21
+ debug: process.env.DEBUG === 'true' || process.env.NODE_ENV !== 'production',
22
+ port: parseInt(process.env.PORT ?? '4000', 10),
23
+ host: process.env.HOST ?? 'localhost',
24
+ });
25
+
26
+ return app;
27
+ }
28
+
29
+ /**
30
+ * Create the HTTP kernel
31
+ */
32
+ export function createKernel(app: Application): Kernel {
33
+ const kernel = new Kernel(app);
34
+
35
+ // Register routes
36
+ registerRoutes();
37
+
38
+ return kernel;
39
+ }
40
+
41
+ // Export pre-configured instances
42
+ export const app = createApplication();
43
+ export const kernel = createKernel(app);
44
+
@@ -0,0 +1,7 @@
1
+ [install]
2
+ # Use exact versions for reproducibility
3
+ exact = true
4
+
5
+ [run]
6
+ # Enable hot reloading during development
7
+ watch = true
@@ -0,0 +1,25 @@
1
+ export default {
2
+ default: 'prisma',
3
+
4
+ connections: {
5
+ prisma: {
6
+ driver: 'prisma',
7
+ // Prisma usually relies on .env DATABASE_URL, but we can put pool config here
8
+ pool: {
9
+ min: 2,
10
+ max: 10,
11
+ idle: 10000,
12
+ },
13
+ },
14
+
15
+ sqlite: {
16
+ driver: 'sqlite',
17
+ // Use ':memory:' for in-memory database or a file path
18
+ filename: ':memory:',
19
+ // Whether to create the database if it doesn't exist
20
+ create: true,
21
+ // Read-only mode
22
+ readonly: false,
23
+ },
24
+ },
25
+ };
@@ -0,0 +1,7 @@
1
+ import { Plugin } from '@framework/plugin/Plugin';
2
+
3
+ const plugins: Plugin[] = [
4
+ // Add your plugins here
5
+ ];
6
+
7
+ export default plugins;
@@ -0,0 +1,158 @@
1
+ /**
2
+ * PhoenixJS - Security Configuration
3
+ *
4
+ * Configure security features: CORS, Helmet headers, Rate limiting, Input sanitization, CSRF.
5
+ */
6
+
7
+ export interface CorsConfig {
8
+ /** Allowed origins - string, array, regex, or function */
9
+ origin: string | string[] | RegExp | ((origin: string) => boolean);
10
+ /** Allowed HTTP methods */
11
+ methods: string[];
12
+ /** Allowed headers */
13
+ allowedHeaders: string[];
14
+ /** Exposed headers */
15
+ exposedHeaders: string[];
16
+ /** Allow credentials (cookies, authorization headers) */
17
+ credentials: boolean;
18
+ /** Preflight request cache duration (seconds) */
19
+ maxAge: number;
20
+ }
21
+
22
+ export interface HelmetConfig {
23
+ /** X-Frame-Options header */
24
+ frameOptions: 'DENY' | 'SAMEORIGIN' | false;
25
+ /** X-Content-Type-Options header */
26
+ contentTypeOptions: boolean;
27
+ /** X-XSS-Protection header */
28
+ xssProtection: boolean;
29
+ /** Strict-Transport-Security header */
30
+ hsts: {
31
+ enabled: boolean;
32
+ maxAge: number;
33
+ includeSubDomains: boolean;
34
+ preload: boolean;
35
+ };
36
+ /** Content-Security-Policy header */
37
+ contentSecurityPolicy: {
38
+ enabled: boolean;
39
+ directives: Record<string, string | string[]>;
40
+ };
41
+ /** Referrer-Policy header */
42
+ referrerPolicy: string;
43
+ /** X-Permitted-Cross-Domain-Policies header */
44
+ crossDomainPolicy: string;
45
+ /** X-Download-Options header (IE) */
46
+ downloadOptions: boolean;
47
+ }
48
+
49
+ export interface RateLimitConfig {
50
+ /** Enable rate limiting */
51
+ enabled: boolean;
52
+ /** Time window in milliseconds */
53
+ windowMs: number;
54
+ /** Maximum requests per window */
55
+ max: number;
56
+ /** Message when rate limit exceeded */
57
+ message: string;
58
+ /** Skip rate limiting for certain requests */
59
+ skip?: (request: Request) => boolean;
60
+ /** Key generator for rate limiting (default: IP) */
61
+ keyGenerator?: (request: Request) => string;
62
+ }
63
+
64
+ export interface SanitizerConfig {
65
+ /** Enable input sanitization */
66
+ enabled: boolean;
67
+ /** Strip HTML tags */
68
+ stripTags: boolean;
69
+ /** Escape HTML entities */
70
+ escapeHtml: boolean;
71
+ /** Trim whitespace */
72
+ trim: boolean;
73
+ /** Fields to skip sanitization */
74
+ skipFields: string[];
75
+ }
76
+
77
+ export interface CsrfConfig {
78
+ /** Enable CSRF protection */
79
+ enabled: boolean;
80
+ /** Cookie name for CSRF token */
81
+ cookieName: string;
82
+ /** Header name for CSRF token */
83
+ headerName: string;
84
+ /** Token length */
85
+ tokenLength: number;
86
+ /** Safe methods (no CSRF check) */
87
+ safeMethods: string[];
88
+ }
89
+
90
+ export interface SecurityConfig {
91
+ cors: CorsConfig;
92
+ helmet: HelmetConfig;
93
+ rateLimit: RateLimitConfig;
94
+ sanitizer: SanitizerConfig;
95
+ csrf: CsrfConfig;
96
+ }
97
+
98
+ const securityConfig: SecurityConfig = {
99
+ cors: {
100
+ origin: '*',
101
+ methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
102
+ allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'X-CSRF-Token'],
103
+ exposedHeaders: ['X-RateLimit-Limit', 'X-RateLimit-Remaining', 'X-RateLimit-Reset'],
104
+ credentials: false,
105
+ maxAge: 86400, // 24 hours
106
+ },
107
+
108
+ helmet: {
109
+ frameOptions: 'DENY',
110
+ contentTypeOptions: true,
111
+ xssProtection: true,
112
+ hsts: {
113
+ enabled: true,
114
+ maxAge: 31536000, // 1 year
115
+ includeSubDomains: true,
116
+ preload: false,
117
+ },
118
+ contentSecurityPolicy: {
119
+ enabled: false, // Disabled by default, enable in production
120
+ directives: {
121
+ defaultSrc: ["'self'"],
122
+ scriptSrc: ["'self'"],
123
+ styleSrc: ["'self'", "'unsafe-inline'"],
124
+ imgSrc: ["'self'", 'data:', 'https:'],
125
+ fontSrc: ["'self'"],
126
+ connectSrc: ["'self'"],
127
+ },
128
+ },
129
+ referrerPolicy: 'no-referrer',
130
+ crossDomainPolicy: 'none',
131
+ downloadOptions: true,
132
+ },
133
+
134
+ rateLimit: {
135
+ enabled: true,
136
+ windowMs: 60000, // 1 minute
137
+ max: 100, // 100 requests per minute
138
+ message: 'Too many requests, please try again later.',
139
+ },
140
+
141
+ sanitizer: {
142
+ enabled: true,
143
+ stripTags: true,
144
+ escapeHtml: true,
145
+ trim: true,
146
+ skipFields: ['password', 'html', 'content'],
147
+ },
148
+
149
+ csrf: {
150
+ enabled: false, // Disabled by default for API-first framework
151
+ cookieName: 'XSRF-TOKEN',
152
+ headerName: 'X-CSRF-Token',
153
+ tokenLength: 32,
154
+ safeMethods: ['GET', 'HEAD', 'OPTIONS'],
155
+ },
156
+ };
157
+
158
+ export default securityConfig;
@@ -0,0 +1,17 @@
1
+ export abstract class Command {
2
+ abstract signature: string;
3
+ abstract description: string;
4
+
5
+ abstract handle(args: Record<string, any>): Promise<void>;
6
+
7
+ protected parseSignature(): { name: string; args: string[] } {
8
+ const parts = this.signature.split(' ');
9
+ const name = parts[0];
10
+ const args = parts.slice(1); // naive parsing for now
11
+ return { name, args };
12
+ }
13
+
14
+ get name(): string {
15
+ return this.parseSignature().name;
16
+ }
17
+ }
@@ -0,0 +1,55 @@
1
+ import { Command } from './Command';
2
+
3
+ export class ConsoleApplication {
4
+ private commands: Map<string, Command> = new Map();
5
+
6
+ register(command: Command) {
7
+ this.commands.set(command.name, command);
8
+ }
9
+
10
+ async run(argv: string[]) {
11
+ const commandName = argv[2]; // bun artisan command:name ...
12
+
13
+ if (!commandName) {
14
+ this.showHelp();
15
+ return;
16
+ }
17
+
18
+ const command = this.commands.get(commandName);
19
+
20
+ if (!command) {
21
+ console.error(`Command "${commandName}" not found.`);
22
+ this.showHelp();
23
+ process.exit(1);
24
+ }
25
+
26
+ // Simple argument parsing
27
+ const args: Record<string, any> = {};
28
+ const signatureArgs = command.signature.split(' ').slice(1);
29
+ const inputArgs = argv.slice(3);
30
+
31
+ signatureArgs.forEach((argName, index) => {
32
+ // Removing {} or [] from arg definition for key name
33
+ const key = argName.replace(/[{}[\]]/g, '');
34
+ args[key] = inputArgs[index];
35
+ });
36
+
37
+ try {
38
+ await command.handle(args);
39
+ } catch (error) {
40
+ console.error('Command failed:', error);
41
+ process.exit(1);
42
+ }
43
+ }
44
+
45
+ private showHelp() {
46
+ console.log('PhoenixJS Framework CLI');
47
+ console.log('\nUsage:');
48
+ console.log(' bun artisan <command> [args]\n');
49
+ console.log('Available commands:');
50
+
51
+ for (const command of this.commands.values()) {
52
+ console.log(` ${command.signature.padEnd(40)} ${command.description}`);
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,16 @@
1
+ import { ConsoleApplication } from './ConsoleApplication';
2
+ import { MakeControllerCommand } from './commands/MakeControllerCommand';
3
+ import { MakeValidatorCommand } from './commands/MakeValidatorCommand';
4
+ import { MakeModelCommand } from './commands/MakeModelCommand';
5
+ import { MakeMiddlewareCommand } from './commands/MakeMiddlewareCommand';
6
+
7
+ const app = new ConsoleApplication();
8
+
9
+ // Register commands
10
+ app.register(new MakeControllerCommand());
11
+ app.register(new MakeValidatorCommand());
12
+ app.register(new MakeModelCommand());
13
+ app.register(new MakeMiddlewareCommand());
14
+
15
+ // Run
16
+ await app.run(process.argv);