@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.
@@ -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, shellsTsContent, productsEdgeContent, productsEjsContent, productsReactContent, productsVueContent, productModelContent, initDbContent, webRoutesContent, apiRoutesContent, serverTsContent, databaseConfigContent, } from '../templates/fullstack-templates.js';
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: hasLocalAero ? 'file:../AeroJS' : '^0.1.0',
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, Inertia Shells, Resources)
272
+ // 13. Presentation Tiers (Views & Resources)
277
273
  if (template === 'fullstack') {
278
274
  addFile('app/views/engine.ts', engineTsContent);
279
- addFile('app/views/shells.ts', shellsTsContent);
280
- addFile('views/edge/products.edge', productsEdgeContent);
281
- addFile('views/ejs/products.ejs', productsEjsContent);
282
- addFile('resources/js/Pages/ProductsReact.tsx', productsReactContent);
283
- addFile('resources/js/Pages/ProductsVue.vue', productsVueContent);
284
- addFile('vite.config.ts', `import { defineConfig } from 'vite';
285
- import { join } from 'node:path';
286
-
287
- export default defineConfig({
288
- root: 'resources',
289
- build: {
290
- outDir: '../public/build',
291
- emptyOutDir: true,
292
- manifest: true,
293
- rollupOptions: {
294
- input: {
295
- app: join(process.cwd(), 'resources/js/app.ts'),
296
- },
297
- },
298
- },
299
- server: {
300
- port: 5173,
301
- strictPort: true,
302
- hmr: {
303
- host: 'localhost',
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
  }