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.
- package/index.ts +196 -0
- package/package.json +31 -0
- package/template/README.md +62 -0
- package/template/app/controllers/ExampleController.ts +61 -0
- package/template/artisan +2 -0
- package/template/bootstrap/app.ts +44 -0
- package/template/bunfig.toml +7 -0
- package/template/config/database.ts +25 -0
- package/template/config/plugins.ts +7 -0
- package/template/config/security.ts +158 -0
- package/template/framework/cli/Command.ts +17 -0
- package/template/framework/cli/ConsoleApplication.ts +55 -0
- package/template/framework/cli/artisan.ts +16 -0
- package/template/framework/cli/commands/MakeControllerCommand.ts +41 -0
- package/template/framework/cli/commands/MakeMiddlewareCommand.ts +41 -0
- package/template/framework/cli/commands/MakeModelCommand.ts +36 -0
- package/template/framework/cli/commands/MakeValidatorCommand.ts +42 -0
- package/template/framework/controller/Controller.ts +222 -0
- package/template/framework/core/Application.ts +208 -0
- package/template/framework/core/Container.ts +100 -0
- package/template/framework/core/Kernel.ts +297 -0
- package/template/framework/database/DatabaseAdapter.ts +18 -0
- package/template/framework/database/PrismaAdapter.ts +65 -0
- package/template/framework/database/SqlAdapter.ts +117 -0
- package/template/framework/gateway/Gateway.ts +109 -0
- package/template/framework/gateway/GatewayManager.ts +150 -0
- package/template/framework/gateway/WebSocketAdapter.ts +159 -0
- package/template/framework/gateway/WebSocketGateway.ts +182 -0
- package/template/framework/http/Request.ts +608 -0
- package/template/framework/http/Response.ts +525 -0
- package/template/framework/http/Server.ts +161 -0
- package/template/framework/http/UploadedFile.ts +145 -0
- package/template/framework/middleware/Middleware.ts +50 -0
- package/template/framework/middleware/Pipeline.ts +89 -0
- package/template/framework/plugin/Plugin.ts +26 -0
- package/template/framework/plugin/PluginManager.ts +61 -0
- package/template/framework/routing/RouteRegistry.ts +185 -0
- package/template/framework/routing/Router.ts +280 -0
- package/template/framework/security/CorsMiddleware.ts +151 -0
- package/template/framework/security/CsrfMiddleware.ts +121 -0
- package/template/framework/security/HelmetMiddleware.ts +138 -0
- package/template/framework/security/InputSanitizerMiddleware.ts +134 -0
- package/template/framework/security/RateLimiterMiddleware.ts +189 -0
- package/template/framework/security/SecurityManager.ts +128 -0
- package/template/framework/validation/Validator.ts +482 -0
- package/template/package.json +24 -0
- package/template/routes/api.ts +56 -0
- package/template/server.ts +29 -0
- 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
|
+
}
|
package/template/artisan
ADDED
|
@@ -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,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,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);
|