@shohaghinfo/aerojs 0.1.0 → 0.1.2
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/AEROJS.md +12 -12
- package/AGENTS.md +709 -709
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +337 -344
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/new.js +51 -51
- package/dist/cli/templates/fullstack-templates.d.ts +3 -7
- package/dist/cli/templates/fullstack-templates.d.ts.map +1 -1
- package/dist/cli/templates/fullstack-templates.js +540 -13
- package/dist/cli/templates/fullstack-templates.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { writeFileSync, mkdirSync, existsSync, readFileSync } from 'node:fs';
|
|
6
6
|
import { join, dirname, resolve, basename } from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
import { engineTsContent,
|
|
8
|
+
import { engineTsContent, appTsContent, indexEdgeContent, initDbContent, webRoutesContent, apiRoutesContent, serverTsContent, databaseConfigContent, } from '../templates/fullstack-templates.js';
|
|
9
9
|
function writeFileWithDir(filePath, content) {
|
|
10
10
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
11
11
|
writeFileSync(filePath, content.trimStart(), 'utf-8');
|
|
@@ -21,9 +21,8 @@ export function initProject(targetDirectory = '.', options = {}) {
|
|
|
21
21
|
createdFiles.push(fullPath);
|
|
22
22
|
};
|
|
23
23
|
// 1. package.json
|
|
24
|
-
const hasLocalAero = existsSync(resolve(root, '../AeroJS/package.json'));
|
|
25
24
|
const dependencies = {
|
|
26
|
-
aerojs:
|
|
25
|
+
aerojs: 'npm:@shohaghinfo/aerojs@^0.1.1',
|
|
27
26
|
knex: '^3.3.0',
|
|
28
27
|
mysql2: '^3.11.0',
|
|
29
28
|
};
|
|
@@ -55,351 +54,345 @@ export function initProject(targetDirectory = '.', options = {}) {
|
|
|
55
54
|
devDependencies,
|
|
56
55
|
}, null, 2) + '\n');
|
|
57
56
|
// 2. tsconfig.json
|
|
58
|
-
addFile('tsconfig.json', `{
|
|
59
|
-
"compilerOptions": {
|
|
60
|
-
"target": "ES2022",
|
|
61
|
-
"module": "NodeNext",
|
|
62
|
-
"moduleResolution": "NodeNext",
|
|
63
|
-
"esModuleInterop": true,
|
|
64
|
-
"strict": true,
|
|
65
|
-
"skipLibCheck": true,
|
|
66
|
-
"outDir": "./dist"
|
|
67
|
-
},
|
|
68
|
-
"include": ["app/**/*", "config/**/*", "routes/**/*", "database/**/*", "server.ts", "tests/**/*"]
|
|
69
|
-
}
|
|
57
|
+
addFile('tsconfig.json', `{
|
|
58
|
+
"compilerOptions": {
|
|
59
|
+
"target": "ES2022",
|
|
60
|
+
"module": "NodeNext",
|
|
61
|
+
"moduleResolution": "NodeNext",
|
|
62
|
+
"esModuleInterop": true,
|
|
63
|
+
"strict": true,
|
|
64
|
+
"skipLibCheck": true,
|
|
65
|
+
"outDir": "./dist"
|
|
66
|
+
},
|
|
67
|
+
"include": ["app/**/*", "config/**/*", "routes/**/*", "database/**/*", "server.ts", "tests/**/*"]
|
|
68
|
+
}
|
|
70
69
|
`);
|
|
71
70
|
// 2.1 vitest.config.ts
|
|
72
|
-
addFile('vitest.config.ts', `import { defineConfig } from 'vitest/config';
|
|
73
|
-
|
|
74
|
-
export default defineConfig({
|
|
75
|
-
test: {
|
|
76
|
-
include: ['tests/**/*.test.ts'],
|
|
77
|
-
},
|
|
78
|
-
});
|
|
71
|
+
addFile('vitest.config.ts', `import { defineConfig } from 'vitest/config';
|
|
72
|
+
|
|
73
|
+
export default defineConfig({
|
|
74
|
+
test: {
|
|
75
|
+
include: ['tests/**/*.test.ts'],
|
|
76
|
+
},
|
|
77
|
+
});
|
|
79
78
|
`);
|
|
80
79
|
// 3. .env & .env.example
|
|
81
|
-
const envContent = `APP_NAME="${projectName}"
|
|
82
|
-
APP_ENV=development
|
|
83
|
-
PORT=3000
|
|
84
|
-
APP_DEBUG=true
|
|
85
|
-
APP_KEY=aerojs_secret_key_${Math.random().toString(36).slice(2)}
|
|
86
|
-
|
|
87
|
-
# Database (MySQL / PostgreSQL / SQLite)
|
|
88
|
-
DB_CONNECTION=mysql
|
|
89
|
-
DB_HOST=127.0.0.1
|
|
90
|
-
DB_PORT=3306
|
|
91
|
-
DB_USER=root
|
|
92
|
-
DB_PASSWORD=
|
|
93
|
-
DB_DATABASE=aerojs_app
|
|
94
|
-
DB_POOL_MIN=2
|
|
95
|
-
DB_POOL_MAX=20
|
|
96
|
-
|
|
97
|
-
SESSION_DRIVER=memory
|
|
98
|
-
QUEUE_DRIVER=memory
|
|
99
|
-
MAIL_DRIVER=log
|
|
80
|
+
const envContent = `APP_NAME="${projectName}"
|
|
81
|
+
APP_ENV=development
|
|
82
|
+
PORT=3000
|
|
83
|
+
APP_DEBUG=true
|
|
84
|
+
APP_KEY=aerojs_secret_key_${Math.random().toString(36).slice(2)}
|
|
85
|
+
|
|
86
|
+
# Database (MySQL / PostgreSQL / SQLite)
|
|
87
|
+
DB_CONNECTION=mysql
|
|
88
|
+
DB_HOST=127.0.0.1
|
|
89
|
+
DB_PORT=3306
|
|
90
|
+
DB_USER=root
|
|
91
|
+
DB_PASSWORD=
|
|
92
|
+
DB_DATABASE=aerojs_app
|
|
93
|
+
DB_POOL_MIN=2
|
|
94
|
+
DB_POOL_MAX=20
|
|
95
|
+
|
|
96
|
+
SESSION_DRIVER=memory
|
|
97
|
+
QUEUE_DRIVER=memory
|
|
98
|
+
MAIL_DRIVER=log
|
|
100
99
|
`;
|
|
101
100
|
addFile('.env', envContent);
|
|
102
101
|
addFile('.env.example', envContent);
|
|
103
102
|
// 4. .gitignore
|
|
104
|
-
addFile('.gitignore', `node_modules/
|
|
105
|
-
dist/
|
|
106
|
-
storage/logs/*
|
|
107
|
-
!storage/logs/.gitkeep
|
|
108
|
-
storage/uploads/*
|
|
109
|
-
!storage/uploads/.gitkeep
|
|
110
|
-
.env
|
|
103
|
+
addFile('.gitignore', `node_modules/
|
|
104
|
+
dist/
|
|
105
|
+
storage/logs/*
|
|
106
|
+
!storage/logs/.gitkeep
|
|
107
|
+
storage/uploads/*
|
|
108
|
+
!storage/uploads/.gitkeep
|
|
109
|
+
.env
|
|
111
110
|
`);
|
|
112
111
|
// 5. Config files
|
|
113
|
-
addFile('config/app.ts', `export const appConfig = {
|
|
114
|
-
name: process.env.APP_NAME || '${projectName}',
|
|
115
|
-
port: parseInt(process.env.PORT || '3000', 10),
|
|
116
|
-
debug: process.env.APP_DEBUG === 'true',
|
|
117
|
-
};
|
|
112
|
+
addFile('config/app.ts', `export const appConfig = {
|
|
113
|
+
name: process.env.APP_NAME || '${projectName}',
|
|
114
|
+
port: parseInt(process.env.PORT || '3000', 10),
|
|
115
|
+
debug: process.env.APP_DEBUG === 'true',
|
|
116
|
+
};
|
|
118
117
|
`);
|
|
119
118
|
addFile('config/database.ts', databaseConfigContent);
|
|
120
|
-
addFile('config/security.ts', `export const securityConfig = {
|
|
121
|
-
csrf: {
|
|
122
|
-
secret: process.env.APP_KEY || 'default-csrf-secret',
|
|
123
|
-
},
|
|
124
|
-
jwt: {
|
|
125
|
-
secret: process.env.APP_KEY || 'default-jwt-secret',
|
|
126
|
-
expiresIn: '7d',
|
|
127
|
-
},
|
|
128
|
-
};
|
|
119
|
+
addFile('config/security.ts', `export const securityConfig = {
|
|
120
|
+
csrf: {
|
|
121
|
+
secret: process.env.APP_KEY || 'default-csrf-secret',
|
|
122
|
+
},
|
|
123
|
+
jwt: {
|
|
124
|
+
secret: process.env.APP_KEY || 'default-jwt-secret',
|
|
125
|
+
expiresIn: '7d',
|
|
126
|
+
},
|
|
127
|
+
};
|
|
129
128
|
`);
|
|
130
129
|
// 6. Controllers
|
|
131
|
-
addFile('app/controllers/HealthController.ts', `import type { AeroContext } from 'aerojs';
|
|
132
|
-
|
|
133
|
-
export class HealthController {
|
|
134
|
-
public async check(ctx: AeroContext): Promise<void> {
|
|
135
|
-
ctx.status(200).json({
|
|
136
|
-
status: 'ok',
|
|
137
|
-
uptime: process.uptime(),
|
|
138
|
-
timestamp: new Date().toISOString(),
|
|
139
|
-
framework: 'AeroJS v0.1.0',
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
}
|
|
130
|
+
addFile('app/controllers/HealthController.ts', `import type { AeroContext } from 'aerojs';
|
|
131
|
+
|
|
132
|
+
export class HealthController {
|
|
133
|
+
public async check(ctx: AeroContext): Promise<void> {
|
|
134
|
+
ctx.status(200).json({
|
|
135
|
+
status: 'ok',
|
|
136
|
+
uptime: process.uptime(),
|
|
137
|
+
timestamp: new Date().toISOString(),
|
|
138
|
+
framework: 'AeroJS v0.1.0',
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
143
142
|
`);
|
|
144
|
-
addFile('app/controllers/UserController.ts', `import type { AeroContext } from 'aerojs';
|
|
145
|
-
import { UserService } from '../services/UserService.js';
|
|
146
|
-
|
|
147
|
-
export class UserController {
|
|
148
|
-
private userService = new UserService();
|
|
149
|
-
|
|
150
|
-
public async index(ctx: AeroContext): Promise<void> {
|
|
151
|
-
const users = await this.userService.getAll();
|
|
152
|
-
ctx.status(200).json({ success: true, data: users });
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
public async show(ctx: AeroContext): Promise<void> {
|
|
156
|
-
const { id } = ctx.params as { id: string };
|
|
157
|
-
const user = await this.userService.findById(id);
|
|
158
|
-
if (!user) {
|
|
159
|
-
ctx.status(404).json({ error: 'User not found' });
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
ctx.status(200).json({ success: true, data: user });
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
public async store(ctx: AeroContext): Promise<void> {
|
|
166
|
-
const data = ctx.body as any;
|
|
167
|
-
const newUser = await this.userService.create(data);
|
|
168
|
-
ctx.status(201).json({ success: true, data: newUser });
|
|
169
|
-
}
|
|
170
|
-
}
|
|
143
|
+
addFile('app/controllers/UserController.ts', `import type { AeroContext } from 'aerojs';
|
|
144
|
+
import { UserService } from '../services/UserService.js';
|
|
145
|
+
|
|
146
|
+
export class UserController {
|
|
147
|
+
private userService = new UserService();
|
|
148
|
+
|
|
149
|
+
public async index(ctx: AeroContext): Promise<void> {
|
|
150
|
+
const users = await this.userService.getAll();
|
|
151
|
+
ctx.status(200).json({ success: true, data: users });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
public async show(ctx: AeroContext): Promise<void> {
|
|
155
|
+
const { id } = ctx.params as { id: string };
|
|
156
|
+
const user = await this.userService.findById(id);
|
|
157
|
+
if (!user) {
|
|
158
|
+
ctx.status(404).json({ error: 'User not found' });
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
ctx.status(200).json({ success: true, data: user });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
public async store(ctx: AeroContext): Promise<void> {
|
|
165
|
+
const data = ctx.body as any;
|
|
166
|
+
const newUser = await this.userService.create(data);
|
|
167
|
+
ctx.status(201).json({ success: true, data: newUser });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
171
170
|
`);
|
|
172
171
|
// 7. Models
|
|
173
|
-
addFile('app/models/User.ts', `import { Model } from 'aerojs';
|
|
174
|
-
|
|
175
|
-
export class User extends Model {
|
|
176
|
-
public static override table = 'users';
|
|
177
|
-
public static override primaryKey = 'id';
|
|
178
|
-
public static override fillable = ['name', 'email', 'role'];
|
|
179
|
-
public static override hidden = ['password'];
|
|
180
|
-
}
|
|
172
|
+
addFile('app/models/User.ts', `import { Model } from 'aerojs';
|
|
173
|
+
|
|
174
|
+
export class User extends Model {
|
|
175
|
+
public static override table = 'users';
|
|
176
|
+
public static override primaryKey = 'id';
|
|
177
|
+
public static override fillable = ['name', 'email', 'role'];
|
|
178
|
+
public static override hidden = ['password'];
|
|
179
|
+
}
|
|
181
180
|
`);
|
|
182
|
-
if (template === 'fullstack') {
|
|
183
|
-
addFile('app/models/Product.ts', productModelContent);
|
|
184
|
-
}
|
|
185
181
|
// 8. Services
|
|
186
|
-
addFile('app/services/UserService.ts', `export interface UserData {
|
|
187
|
-
id: string | number;
|
|
188
|
-
name: string;
|
|
189
|
-
email: string;
|
|
190
|
-
role: string;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export class UserService {
|
|
194
|
-
private users: UserData[] = [
|
|
195
|
-
{ id: 1, name: 'Admin User', email: 'admin@aerojs.dev', role: 'admin' },
|
|
196
|
-
{ id: 2, name: 'Lead Developer', email: 'dev@aerojs.dev', role: 'developer' },
|
|
197
|
-
];
|
|
198
|
-
|
|
199
|
-
public async getAll(): Promise<UserData[]> {
|
|
200
|
-
return this.users;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
public async findById(id: string | number): Promise<UserData | null> {
|
|
204
|
-
return this.users.find((u) => String(u.id) === String(id)) || null;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
public async create(data: Omit<UserData, 'id'>): Promise<UserData> {
|
|
208
|
-
const newUser: UserData = {
|
|
209
|
-
id: this.users.length + 1,
|
|
210
|
-
role: data.role || 'user',
|
|
211
|
-
name: data.name,
|
|
212
|
-
email: data.email,
|
|
213
|
-
};
|
|
214
|
-
this.users.push(newUser);
|
|
215
|
-
return newUser;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
182
|
+
addFile('app/services/UserService.ts', `export interface UserData {
|
|
183
|
+
id: string | number;
|
|
184
|
+
name: string;
|
|
185
|
+
email: string;
|
|
186
|
+
role: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export class UserService {
|
|
190
|
+
private users: UserData[] = [
|
|
191
|
+
{ id: 1, name: 'Admin User', email: 'admin@aerojs.dev', role: 'admin' },
|
|
192
|
+
{ id: 2, name: 'Lead Developer', email: 'dev@aerojs.dev', role: 'developer' },
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
public async getAll(): Promise<UserData[]> {
|
|
196
|
+
return this.users;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
public async findById(id: string | number): Promise<UserData | null> {
|
|
200
|
+
return this.users.find((u) => String(u.id) === String(id)) || null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
public async create(data: Omit<UserData, 'id'>): Promise<UserData> {
|
|
204
|
+
const newUser: UserData = {
|
|
205
|
+
id: this.users.length + 1,
|
|
206
|
+
role: data.role || 'user',
|
|
207
|
+
name: data.name,
|
|
208
|
+
email: data.email,
|
|
209
|
+
};
|
|
210
|
+
this.users.push(newUser);
|
|
211
|
+
return newUser;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
218
214
|
`);
|
|
219
215
|
// 9. Validators
|
|
220
|
-
addFile('app/validators/UserValidator.ts', `export const createUserSchema = {
|
|
221
|
-
body: {
|
|
222
|
-
type: 'object',
|
|
223
|
-
required: ['name', 'email'],
|
|
224
|
-
properties: {
|
|
225
|
-
name: { type: 'string', minLength: 2 },
|
|
226
|
-
email: { type: 'string', format: 'email' },
|
|
227
|
-
role: { type: 'string', enum: ['admin', 'developer', 'user'] },
|
|
228
|
-
},
|
|
229
|
-
},
|
|
230
|
-
};
|
|
216
|
+
addFile('app/validators/UserValidator.ts', `export const createUserSchema = {
|
|
217
|
+
body: {
|
|
218
|
+
type: 'object',
|
|
219
|
+
required: ['name', 'email'],
|
|
220
|
+
properties: {
|
|
221
|
+
name: { type: 'string', minLength: 2 },
|
|
222
|
+
email: { type: 'string', format: 'email' },
|
|
223
|
+
role: { type: 'string', enum: ['admin', 'developer', 'user'] },
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
};
|
|
231
227
|
`);
|
|
232
228
|
// 10. Middlewares
|
|
233
|
-
addFile('app/middleware/AuthMiddleware.ts', `import type { AeroContext, NextFunction } from 'aerojs';
|
|
234
|
-
|
|
235
|
-
export async function authMiddleware(ctx: AeroContext, next: NextFunction): Promise<void> {
|
|
236
|
-
const authHeader = ctx.req.get('authorization');
|
|
237
|
-
if (!authHeader) {
|
|
238
|
-
ctx.status(401).json({ error: 'Unauthorized: Missing Authorization header' });
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
await next();
|
|
242
|
-
}
|
|
229
|
+
addFile('app/middleware/AuthMiddleware.ts', `import type { AeroContext, NextFunction } from 'aerojs';
|
|
230
|
+
|
|
231
|
+
export async function authMiddleware(ctx: AeroContext, next: NextFunction): Promise<void> {
|
|
232
|
+
const authHeader = ctx.req.get('authorization');
|
|
233
|
+
if (!authHeader) {
|
|
234
|
+
ctx.status(401).json({ error: 'Unauthorized: Missing Authorization header' });
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
await next();
|
|
238
|
+
}
|
|
243
239
|
`);
|
|
244
240
|
// 11. Jobs
|
|
245
|
-
addFile('app/jobs/WelcomeEmailJob.ts', `import { Job } from 'aerojs';
|
|
246
|
-
|
|
247
|
-
export class WelcomeEmailJob extends Job {
|
|
248
|
-
public async handle(): Promise<void> {
|
|
249
|
-
console.log(\`[Background Job] Sending welcome email to: \${this.data.email}\`);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
241
|
+
addFile('app/jobs/WelcomeEmailJob.ts', `import { Job } from 'aerojs';
|
|
242
|
+
|
|
243
|
+
export class WelcomeEmailJob extends Job {
|
|
244
|
+
public async handle(): Promise<void> {
|
|
245
|
+
console.log(\`[Background Job] Sending welcome email to: \${this.data.email}\`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
252
248
|
`);
|
|
253
249
|
// 12. Migrations
|
|
254
|
-
addFile('database/migrations/2026_09_30_000000_create_users_table.ts', `import { Schema, type Migration, type TableBlueprint } from 'aerojs';
|
|
255
|
-
|
|
256
|
-
export default class CreateUsersTable implements Migration {
|
|
257
|
-
public name = '2026_09_30_000000_create_users_table';
|
|
258
|
-
|
|
259
|
-
public async up(): Promise<void> {
|
|
260
|
-
await Schema.create('users', (table: TableBlueprint) => {
|
|
261
|
-
table.increments('id');
|
|
262
|
-
table.string('name').notNull();
|
|
263
|
-
table.string('email').notNull().unique();
|
|
264
|
-
table.string('role').defaultTo('user');
|
|
265
|
-
table.timestamps();
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
public async down(): Promise<void> {
|
|
270
|
-
await Schema.dropIfExists('users');
|
|
271
|
-
}
|
|
272
|
-
}
|
|
250
|
+
addFile('database/migrations/2026_09_30_000000_create_users_table.ts', `import { Schema, type Migration, type TableBlueprint } from 'aerojs';
|
|
251
|
+
|
|
252
|
+
export default class CreateUsersTable implements Migration {
|
|
253
|
+
public name = '2026_09_30_000000_create_users_table';
|
|
254
|
+
|
|
255
|
+
public async up(): Promise<void> {
|
|
256
|
+
await Schema.create('users', (table: TableBlueprint) => {
|
|
257
|
+
table.increments('id');
|
|
258
|
+
table.string('name').notNull();
|
|
259
|
+
table.string('email').notNull().unique();
|
|
260
|
+
table.string('role').defaultTo('user');
|
|
261
|
+
table.timestamps();
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
public async down(): Promise<void> {
|
|
266
|
+
await Schema.dropIfExists('users');
|
|
267
|
+
}
|
|
268
|
+
}
|
|
273
269
|
`);
|
|
274
270
|
// 12.1 Database Initialization
|
|
275
271
|
addFile('database/init-db.ts', initDbContent);
|
|
276
|
-
// 13. Presentation Tiers (Views
|
|
272
|
+
// 13. Presentation Tiers (Views & Resources)
|
|
277
273
|
if (template === 'fullstack') {
|
|
278
274
|
addFile('app/views/engine.ts', engineTsContent);
|
|
279
|
-
addFile('
|
|
280
|
-
addFile('
|
|
281
|
-
addFile('
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
},
|
|
305
|
-
},
|
|
306
|
-
});
|
|
275
|
+
addFile('views/index.edge', indexEdgeContent);
|
|
276
|
+
addFile('resources/js/app.ts', appTsContent);
|
|
277
|
+
addFile('vite.config.ts', `import { defineConfig } from 'vite';
|
|
278
|
+
import { join } from 'node:path';
|
|
279
|
+
|
|
280
|
+
export default defineConfig({
|
|
281
|
+
root: 'resources',
|
|
282
|
+
build: {
|
|
283
|
+
outDir: '../public/build',
|
|
284
|
+
emptyOutDir: true,
|
|
285
|
+
manifest: true,
|
|
286
|
+
rollupOptions: {
|
|
287
|
+
input: {
|
|
288
|
+
app: join(process.cwd(), 'resources/js/app.ts'),
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
server: {
|
|
293
|
+
port: 5173,
|
|
294
|
+
strictPort: true,
|
|
295
|
+
hmr: {
|
|
296
|
+
host: 'localhost',
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
});
|
|
307
300
|
`);
|
|
308
301
|
}
|
|
309
302
|
// 14. Routes
|
|
310
|
-
addFile('routes/api.ts', template === 'fullstack' ? apiRoutesContent : `import type { Router } from 'aerojs';
|
|
311
|
-
import { HealthController } from '../app/controllers/HealthController.js';
|
|
312
|
-
import { UserController } from '../app/controllers/UserController.js';
|
|
313
|
-
import { createUserSchema } from '../app/validators/UserValidator.js';
|
|
314
|
-
|
|
315
|
-
export function registerApiRoutes(router: Router): void {
|
|
316
|
-
router.group('/api', (api) => {
|
|
317
|
-
api.get('/health', [HealthController, 'check']);
|
|
318
|
-
|
|
319
|
-
api.group('/users', (users) => {
|
|
320
|
-
users.get('/', [UserController, 'index']);
|
|
321
|
-
users.get('/:id', [UserController, 'show']);
|
|
322
|
-
users.post('/', [UserController, 'store']).schema(createUserSchema);
|
|
323
|
-
});
|
|
324
|
-
});
|
|
325
|
-
}
|
|
303
|
+
addFile('routes/api.ts', template === 'fullstack' ? apiRoutesContent : `import type { Router } from 'aerojs';
|
|
304
|
+
import { HealthController } from '../app/controllers/HealthController.js';
|
|
305
|
+
import { UserController } from '../app/controllers/UserController.js';
|
|
306
|
+
import { createUserSchema } from '../app/validators/UserValidator.js';
|
|
307
|
+
|
|
308
|
+
export function registerApiRoutes(router: Router): void {
|
|
309
|
+
router.group('/api', (api) => {
|
|
310
|
+
api.get('/health', [HealthController, 'check']);
|
|
311
|
+
|
|
312
|
+
api.group('/users', (users) => {
|
|
313
|
+
users.get('/', [UserController, 'index']);
|
|
314
|
+
users.get('/:id', [UserController, 'show']);
|
|
315
|
+
users.post('/', [UserController, 'store']).schema(createUserSchema);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
}
|
|
326
319
|
`);
|
|
327
|
-
addFile('routes/web.ts', template === 'fullstack' ? webRoutesContent : `import type { Router, AeroContext } from 'aerojs';
|
|
328
|
-
|
|
329
|
-
export function registerWebRoutes(router: Router): void {
|
|
330
|
-
router.get('/', (ctx: AeroContext) => {
|
|
331
|
-
ctx.html('<h1>Welcome to AeroJS</h1>');
|
|
332
|
-
});
|
|
333
|
-
}
|
|
320
|
+
addFile('routes/web.ts', template === 'fullstack' ? webRoutesContent : `import type { Router, AeroContext } from 'aerojs';
|
|
321
|
+
|
|
322
|
+
export function registerWebRoutes(router: Router): void {
|
|
323
|
+
router.get('/', (ctx: AeroContext) => {
|
|
324
|
+
ctx.html('<h1>Welcome to AeroJS</h1>');
|
|
325
|
+
});
|
|
326
|
+
}
|
|
334
327
|
`);
|
|
335
328
|
// 15. Server entry point
|
|
336
|
-
addFile('server.ts', template === 'fullstack' ? serverTsContent : `import { AeroJS } from 'aerojs';
|
|
337
|
-
import { appConfig } from './config/app.js';
|
|
338
|
-
import { registerApiRoutes } from './routes/api.js';
|
|
339
|
-
import { registerWebRoutes } from './routes/web.js';
|
|
340
|
-
import { join } from 'node:path';
|
|
341
|
-
import { initDatabase } from './database/init-db.js';
|
|
342
|
-
|
|
343
|
-
// Initialize Database connection
|
|
344
|
-
await initDatabase();
|
|
345
|
-
|
|
346
|
-
const app = new AeroJS({
|
|
347
|
-
debug: appConfig.debug,
|
|
348
|
-
trustProxy: true,
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
// Middlewares
|
|
352
|
-
app.useCors();
|
|
353
|
-
app.useSecurityHeaders();
|
|
354
|
-
app.serveStatic('/public', join(process.cwd(), 'public'));
|
|
355
|
-
|
|
356
|
-
// Register Routes
|
|
357
|
-
registerWebRoutes(app.router);
|
|
358
|
-
registerApiRoutes(app.router);
|
|
359
|
-
|
|
360
|
-
// Start server (only when not in test runner)
|
|
361
|
-
if (process.env.NODE_ENV !== 'test') {
|
|
362
|
-
app.listen(appConfig.port, () => {
|
|
363
|
-
console.log(\`\\n🚀 AeroJS Server running at http://localhost:\${appConfig.port}\`);
|
|
364
|
-
console.log(\`👉 Health Check: http://localhost:\${appConfig.port}/api/health\`);
|
|
365
|
-
console.log(\`👉 Users API: http://localhost:\${appConfig.port}/api/users\\n\`);
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
export default app;
|
|
329
|
+
addFile('server.ts', template === 'fullstack' ? serverTsContent : `import { AeroJS } from 'aerojs';
|
|
330
|
+
import { appConfig } from './config/app.js';
|
|
331
|
+
import { registerApiRoutes } from './routes/api.js';
|
|
332
|
+
import { registerWebRoutes } from './routes/web.js';
|
|
333
|
+
import { join } from 'node:path';
|
|
334
|
+
import { initDatabase } from './database/init-db.js';
|
|
335
|
+
|
|
336
|
+
// Initialize Database connection
|
|
337
|
+
await initDatabase();
|
|
338
|
+
|
|
339
|
+
const app = new AeroJS({
|
|
340
|
+
debug: appConfig.debug,
|
|
341
|
+
trustProxy: true,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// Middlewares
|
|
345
|
+
app.useCors();
|
|
346
|
+
app.useSecurityHeaders();
|
|
347
|
+
app.serveStatic('/public', join(process.cwd(), 'public'));
|
|
348
|
+
|
|
349
|
+
// Register Routes
|
|
350
|
+
registerWebRoutes(app.router);
|
|
351
|
+
registerApiRoutes(app.router);
|
|
352
|
+
|
|
353
|
+
// Start server (only when not in test runner)
|
|
354
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
355
|
+
app.listen(appConfig.port, () => {
|
|
356
|
+
console.log(\`\\n🚀 AeroJS Server running at http://localhost:\${appConfig.port}\`);
|
|
357
|
+
console.log(\`👉 Health Check: http://localhost:\${appConfig.port}/api/health\`);
|
|
358
|
+
console.log(\`👉 Users API: http://localhost:\${appConfig.port}/api/users\\n\`);
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export default app;
|
|
370
363
|
`);
|
|
371
364
|
// 16. Storage & Public placeholder
|
|
372
365
|
addFile('public/robots.txt', `User-agent: *\nAllow: /\n`);
|
|
373
366
|
addFile('storage/logs/.gitkeep', '');
|
|
374
367
|
addFile('storage/uploads/.gitkeep', '');
|
|
375
368
|
// 17. Tests
|
|
376
|
-
addFile('tests/app.test.ts', `import { describe, it, expect } from 'vitest';
|
|
377
|
-
import { createTestClient } from 'aerojs/testing';
|
|
378
|
-
import app from '../server.js';
|
|
379
|
-
|
|
380
|
-
describe('AeroJS Application End-to-End Tests', () => {
|
|
381
|
-
const client = createTestClient(app);
|
|
382
|
-
|
|
383
|
-
it('GET / returns HTML landing page', async () => {
|
|
384
|
-
const res = await client.get('/');
|
|
385
|
-
expect(res.status).toBe(200);
|
|
386
|
-
expect(res.text()).toContain('Welcome to AeroJS');
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
it('GET /api/health returns health status', async () => {
|
|
390
|
-
const res = await client.get('/api/health');
|
|
391
|
-
expect(res.status).toBe(200);
|
|
392
|
-
expect(res.json().status).toBe('ok');
|
|
393
|
-
expect(res.json().framework).toContain('AeroJS');
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
it('GET /api/users returns list of users', async () => {
|
|
397
|
-
const res = await client.get('/api/users');
|
|
398
|
-
expect(res.status).toBe(200);
|
|
399
|
-
expect(res.json().success).toBe(true);
|
|
400
|
-
expect(res.json().data.length).toBeGreaterThan(0);
|
|
401
|
-
});
|
|
402
|
-
});
|
|
369
|
+
addFile('tests/app.test.ts', `import { describe, it, expect } from 'vitest';
|
|
370
|
+
import { createTestClient } from 'aerojs/testing';
|
|
371
|
+
import app from '../server.js';
|
|
372
|
+
|
|
373
|
+
describe('AeroJS Application End-to-End Tests', () => {
|
|
374
|
+
const client = createTestClient(app);
|
|
375
|
+
|
|
376
|
+
it('GET / returns HTML landing page', async () => {
|
|
377
|
+
const res = await client.get('/');
|
|
378
|
+
expect(res.status).toBe(200);
|
|
379
|
+
expect(res.text()).toContain('Welcome to AeroJS');
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('GET /api/health returns health status', async () => {
|
|
383
|
+
const res = await client.get('/api/health');
|
|
384
|
+
expect(res.status).toBe(200);
|
|
385
|
+
expect(res.json().status).toBe('ok');
|
|
386
|
+
expect(res.json().framework).toContain('AeroJS');
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('GET /api/users returns list of users', async () => {
|
|
390
|
+
const res = await client.get('/api/users');
|
|
391
|
+
expect(res.status).toBe(200);
|
|
392
|
+
expect(res.json().success).toBe(true);
|
|
393
|
+
expect(res.json().data.length).toBeGreaterThan(0);
|
|
394
|
+
});
|
|
395
|
+
});
|
|
403
396
|
`);
|
|
404
397
|
// 18. AI Agent Guidelines (AGENTS.md & AEROJS.md)
|
|
405
398
|
let agentsMdContent = '';
|
|
@@ -423,48 +416,48 @@ describe('AeroJS Application End-to-End Tests', () => {
|
|
|
423
416
|
addFile('AGENTS.md', agentsMdContent);
|
|
424
417
|
}
|
|
425
418
|
else {
|
|
426
|
-
addFile('AGENTS.md', `# AeroJS AI Agent Architecture & Coding Guidelines
|
|
427
|
-
|
|
428
|
-
> **Notice for AI Coding Assistants (Gemini, Claude, Cursor, Windsurf, Copilot, ChatGPT):**
|
|
429
|
-
> This file is your canonical reference manual for writing correct, idiomatic, high-performance TypeScript code for applications built on **AeroJS**. Always adhere strictly to the conventions, patterns, and APIs documented here.
|
|
430
|
-
|
|
431
|
-
## 1. Core Architecture Pattern
|
|
432
|
-
AeroJS uses a layered Full-Stack MVC & Service-Oriented Architecture:
|
|
433
|
-
\`\`\`
|
|
434
|
-
Router -> Middleware -> Validator -> Controller -> Service -> Model / DB -> View / Response
|
|
435
|
-
\`\`\`
|
|
436
|
-
|
|
437
|
-
## 2. Directory Structure Conventions
|
|
438
|
-
- \`app/controllers/\`: HTTP handlers. Keep lean; delegate logic to services.
|
|
439
|
-
- \`app/services/\`: Reusable business logic, database transactions, external APIs.
|
|
440
|
-
- \`app/models/\`: Active Record Models extending \`Model\`.
|
|
441
|
-
- \`app/validators/\`: JSON Schema validation definitions.
|
|
442
|
-
- \`app/middleware/\`: Interceptors \`(ctx: AeroContext, next: NextFunction) => Promise<void>\`.
|
|
443
|
-
- \`app/jobs/\`: Asynchronous background queue jobs extending \`Job\`.
|
|
444
|
-
- \`config/\`: Environment configurations (\`app.ts\`, \`database.ts\`, \`security.ts\`).
|
|
445
|
-
- \`database/migrations/\`: Migrations using \`Schema.create('table', (table) => ...)\`.
|
|
446
|
-
- \`routes/\`: Route groups (\`api.ts\`, \`web.ts\`).
|
|
447
|
-
|
|
448
|
-
## 3. Verification Commands
|
|
449
|
-
Always verify your code changes by running:
|
|
450
|
-
\`\`\`bash
|
|
451
|
-
npm run build
|
|
452
|
-
npm test
|
|
453
|
-
\`\`\`
|
|
419
|
+
addFile('AGENTS.md', `# AeroJS AI Agent Architecture & Coding Guidelines
|
|
420
|
+
|
|
421
|
+
> **Notice for AI Coding Assistants (Gemini, Claude, Cursor, Windsurf, Copilot, ChatGPT):**
|
|
422
|
+
> This file is your canonical reference manual for writing correct, idiomatic, high-performance TypeScript code for applications built on **AeroJS**. Always adhere strictly to the conventions, patterns, and APIs documented here.
|
|
423
|
+
|
|
424
|
+
## 1. Core Architecture Pattern
|
|
425
|
+
AeroJS uses a layered Full-Stack MVC & Service-Oriented Architecture:
|
|
426
|
+
\`\`\`
|
|
427
|
+
Router -> Middleware -> Validator -> Controller -> Service -> Model / DB -> View / Response
|
|
428
|
+
\`\`\`
|
|
429
|
+
|
|
430
|
+
## 2. Directory Structure Conventions
|
|
431
|
+
- \`app/controllers/\`: HTTP handlers. Keep lean; delegate logic to services.
|
|
432
|
+
- \`app/services/\`: Reusable business logic, database transactions, external APIs.
|
|
433
|
+
- \`app/models/\`: Active Record Models extending \`Model\`.
|
|
434
|
+
- \`app/validators/\`: JSON Schema validation definitions.
|
|
435
|
+
- \`app/middleware/\`: Interceptors \`(ctx: AeroContext, next: NextFunction) => Promise<void>\`.
|
|
436
|
+
- \`app/jobs/\`: Asynchronous background queue jobs extending \`Job\`.
|
|
437
|
+
- \`config/\`: Environment configurations (\`app.ts\`, \`database.ts\`, \`security.ts\`).
|
|
438
|
+
- \`database/migrations/\`: Migrations using \`Schema.create('table', (table) => ...)\`.
|
|
439
|
+
- \`routes/\`: Route groups (\`api.ts\`, \`web.ts\`).
|
|
440
|
+
|
|
441
|
+
## 3. Verification Commands
|
|
442
|
+
Always verify your code changes by running:
|
|
443
|
+
\`\`\`bash
|
|
444
|
+
npm run build
|
|
445
|
+
npm test
|
|
446
|
+
\`\`\`
|
|
454
447
|
`);
|
|
455
448
|
}
|
|
456
|
-
addFile('AEROJS.md', `# AeroJS Architecture & Developer Guide
|
|
457
|
-
|
|
458
|
-
Please see [AGENTS.md](./AGENTS.md) for the complete reference manual, covering:
|
|
459
|
-
- Layered Architecture (MVC + Service Layer)
|
|
460
|
-
- Routing & Controller Tuples (\`[Controller, 'method']\`)
|
|
461
|
-
- Database Active Record ORM & QueryBuilder (\`Model\`, \`DB.table\`)
|
|
462
|
-
- Database Migrations (\`Schema.create\`, \`TableBlueprint\`)
|
|
463
|
-
- MySQL, PostgreSQL, SQLite, Prisma, and Drizzle setups
|
|
464
|
-
- Frontend Integrations (Inertia.js React/Vue 3, Edge.js, EJS, SSR)
|
|
465
|
-
- Request Validation & Security Middleware
|
|
466
|
-
- Background Queue Jobs & Mailers
|
|
467
|
-
- In-process Testing with \`createTestClient\`
|
|
449
|
+
addFile('AEROJS.md', `# AeroJS Architecture & Developer Guide
|
|
450
|
+
|
|
451
|
+
Please see [AGENTS.md](./AGENTS.md) for the complete reference manual, covering:
|
|
452
|
+
- Layered Architecture (MVC + Service Layer)
|
|
453
|
+
- Routing & Controller Tuples (\`[Controller, 'method']\`)
|
|
454
|
+
- Database Active Record ORM & QueryBuilder (\`Model\`, \`DB.table\`)
|
|
455
|
+
- Database Migrations (\`Schema.create\`, \`TableBlueprint\`)
|
|
456
|
+
- MySQL, PostgreSQL, SQLite, Prisma, and Drizzle setups
|
|
457
|
+
- Frontend Integrations (Inertia.js React/Vue 3, Edge.js, EJS, SSR)
|
|
458
|
+
- Request Validation & Security Middleware
|
|
459
|
+
- Background Queue Jobs & Mailers
|
|
460
|
+
- In-process Testing with \`createTestClient\`
|
|
468
461
|
`);
|
|
469
462
|
return createdFiles;
|
|
470
463
|
}
|