servcraft 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -3
- package/ROADMAP.md +320 -0
- package/dist/cli/index.cjs +2201 -74
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +2197 -74
- package/dist/cli/index.js.map +1 -1
- package/package.json +2 -2
- package/src/cli/commands/add-module.ts +52 -8
- package/src/cli/commands/init.ts +441 -65
- package/src/cli/index.ts +4 -0
package/src/cli/commands/init.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { ensureDir, writeFile, error, warn } from '../utils/helpers.js';
|
|
|
10
10
|
interface InitOptions {
|
|
11
11
|
name: string;
|
|
12
12
|
language: 'typescript' | 'javascript';
|
|
13
|
+
moduleSystem: 'esm' | 'commonjs';
|
|
13
14
|
database: 'postgresql' | 'mysql' | 'sqlite' | 'mongodb' | 'none';
|
|
14
15
|
orm: 'prisma' | 'mongoose' | 'none';
|
|
15
16
|
validator: 'zod' | 'joi' | 'yup';
|
|
@@ -23,11 +24,20 @@ export const initCommand = new Command('init')
|
|
|
23
24
|
.option('-y, --yes', 'Skip prompts and use defaults')
|
|
24
25
|
.option('--ts, --typescript', 'Use TypeScript (default)')
|
|
25
26
|
.option('--js, --javascript', 'Use JavaScript')
|
|
27
|
+
.option('--esm', 'Use ES Modules (import/export) - default')
|
|
28
|
+
.option('--cjs, --commonjs', 'Use CommonJS (require/module.exports)')
|
|
26
29
|
.option('--db <database>', 'Database type (postgresql, mysql, sqlite, mongodb, none)')
|
|
27
30
|
.action(
|
|
28
31
|
async (
|
|
29
32
|
name?: string,
|
|
30
|
-
cmdOptions?: {
|
|
33
|
+
cmdOptions?: {
|
|
34
|
+
yes?: boolean;
|
|
35
|
+
typescript?: boolean;
|
|
36
|
+
javascript?: boolean;
|
|
37
|
+
esm?: boolean;
|
|
38
|
+
commonjs?: boolean;
|
|
39
|
+
db?: string;
|
|
40
|
+
}
|
|
31
41
|
) => {
|
|
32
42
|
console.log(
|
|
33
43
|
chalk.blue(`
|
|
@@ -46,6 +56,7 @@ export const initCommand = new Command('init')
|
|
|
46
56
|
options = {
|
|
47
57
|
name: name || 'my-servcraft-app',
|
|
48
58
|
language: cmdOptions.javascript ? 'javascript' : 'typescript',
|
|
59
|
+
moduleSystem: cmdOptions.commonjs ? 'commonjs' : 'esm',
|
|
49
60
|
database: db,
|
|
50
61
|
orm: db === 'mongodb' ? 'mongoose' : db === 'none' ? 'none' : 'prisma',
|
|
51
62
|
validator: 'zod',
|
|
@@ -75,6 +86,16 @@ export const initCommand = new Command('init')
|
|
|
75
86
|
],
|
|
76
87
|
default: 'typescript',
|
|
77
88
|
},
|
|
89
|
+
{
|
|
90
|
+
type: 'list',
|
|
91
|
+
name: 'moduleSystem',
|
|
92
|
+
message: 'Select module system:',
|
|
93
|
+
choices: [
|
|
94
|
+
{ name: 'ESM (import/export) - Recommended', value: 'esm' },
|
|
95
|
+
{ name: 'CommonJS (require/module.exports)', value: 'commonjs' },
|
|
96
|
+
],
|
|
97
|
+
default: 'esm',
|
|
98
|
+
},
|
|
78
99
|
{
|
|
79
100
|
type: 'list',
|
|
80
101
|
name: 'database',
|
|
@@ -150,10 +171,10 @@ export const initCommand = new Command('init')
|
|
|
150
171
|
|
|
151
172
|
// Generate tsconfig or jsconfig
|
|
152
173
|
if (options.language === 'typescript') {
|
|
153
|
-
await writeFile(path.join(projectDir, 'tsconfig.json'), generateTsConfig());
|
|
154
|
-
await writeFile(path.join(projectDir, 'tsup.config.ts'), generateTsupConfig());
|
|
174
|
+
await writeFile(path.join(projectDir, 'tsconfig.json'), generateTsConfig(options));
|
|
175
|
+
await writeFile(path.join(projectDir, 'tsup.config.ts'), generateTsupConfig(options));
|
|
155
176
|
} else {
|
|
156
|
-
await writeFile(path.join(projectDir, 'jsconfig.json'), generateJsConfig());
|
|
177
|
+
await writeFile(path.join(projectDir, 'jsconfig.json'), generateJsConfig(options));
|
|
157
178
|
}
|
|
158
179
|
|
|
159
180
|
// Generate .env files
|
|
@@ -171,7 +192,10 @@ export const initCommand = new Command('init')
|
|
|
171
192
|
);
|
|
172
193
|
|
|
173
194
|
// Create directory structure
|
|
174
|
-
|
|
195
|
+
// For JS: .js for ESM, .cjs for CommonJS
|
|
196
|
+
// For TS: always .ts (output format handled by tsup)
|
|
197
|
+
const ext =
|
|
198
|
+
options.language === 'typescript' ? 'ts' : options.moduleSystem === 'esm' ? 'js' : 'cjs';
|
|
175
199
|
const dirs = [
|
|
176
200
|
'src/core',
|
|
177
201
|
'src/config',
|
|
@@ -207,6 +231,30 @@ export const initCommand = new Command('init')
|
|
|
207
231
|
generateLoggerFile(options)
|
|
208
232
|
);
|
|
209
233
|
|
|
234
|
+
// Generate config file
|
|
235
|
+
await writeFile(
|
|
236
|
+
path.join(projectDir, `src/config/index.${ext}`),
|
|
237
|
+
generateConfigFile(options)
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
// Generate middleware file
|
|
241
|
+
await writeFile(
|
|
242
|
+
path.join(projectDir, `src/middleware/index.${ext}`),
|
|
243
|
+
generateMiddlewareFile(options)
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
// Generate utils file
|
|
247
|
+
await writeFile(
|
|
248
|
+
path.join(projectDir, `src/utils/index.${ext}`),
|
|
249
|
+
generateUtilsFile(options)
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
// Generate types file
|
|
253
|
+
await writeFile(
|
|
254
|
+
path.join(projectDir, `src/types/index.${ext}`),
|
|
255
|
+
generateTypesFile(options)
|
|
256
|
+
);
|
|
257
|
+
|
|
210
258
|
// Generate database files based on ORM choice
|
|
211
259
|
if (options.orm === 'prisma') {
|
|
212
260
|
await writeFile(
|
|
@@ -278,19 +326,46 @@ export const initCommand = new Command('init')
|
|
|
278
326
|
|
|
279
327
|
function generatePackageJson(options: InitOptions): Record<string, unknown> {
|
|
280
328
|
const isTS = options.language === 'typescript';
|
|
329
|
+
const isESM = options.moduleSystem === 'esm';
|
|
330
|
+
|
|
331
|
+
// Determine dev command based on language and module system
|
|
332
|
+
let devCommand: string;
|
|
333
|
+
if (isTS) {
|
|
334
|
+
devCommand = 'tsx watch src/index.ts';
|
|
335
|
+
} else if (isESM) {
|
|
336
|
+
devCommand = 'node --watch src/index.js';
|
|
337
|
+
} else {
|
|
338
|
+
devCommand = 'node --watch src/index.cjs';
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Determine start command
|
|
342
|
+
let startCommand: string;
|
|
343
|
+
if (isTS) {
|
|
344
|
+
startCommand = isESM ? 'node dist/index.js' : 'node dist/index.cjs';
|
|
345
|
+
} else if (isESM) {
|
|
346
|
+
startCommand = 'node src/index.js';
|
|
347
|
+
} else {
|
|
348
|
+
startCommand = 'node src/index.cjs';
|
|
349
|
+
}
|
|
281
350
|
|
|
282
351
|
const pkg: Record<string, unknown> = {
|
|
283
352
|
name: options.name,
|
|
284
353
|
version: '0.1.0',
|
|
285
354
|
description: 'A Servcraft application',
|
|
286
|
-
main: isTS
|
|
287
|
-
|
|
355
|
+
main: isTS
|
|
356
|
+
? isESM
|
|
357
|
+
? 'dist/index.js'
|
|
358
|
+
: 'dist/index.cjs'
|
|
359
|
+
: isESM
|
|
360
|
+
? 'src/index.js'
|
|
361
|
+
: 'src/index.cjs',
|
|
362
|
+
...(isESM && { type: 'module' }),
|
|
288
363
|
scripts: {
|
|
289
|
-
dev:
|
|
364
|
+
dev: devCommand,
|
|
290
365
|
build: isTS ? 'tsup' : 'echo "No build needed for JS"',
|
|
291
|
-
start:
|
|
366
|
+
start: startCommand,
|
|
292
367
|
test: 'vitest',
|
|
293
|
-
lint: isTS ? 'eslint src --ext .ts' : 'eslint src --ext .js',
|
|
368
|
+
lint: isTS ? 'eslint src --ext .ts' : 'eslint src --ext .js,.cjs',
|
|
294
369
|
},
|
|
295
370
|
dependencies: {
|
|
296
371
|
fastify: '^4.28.1',
|
|
@@ -361,13 +436,15 @@ function generatePackageJson(options: InitOptions): Record<string, unknown> {
|
|
|
361
436
|
return pkg;
|
|
362
437
|
}
|
|
363
438
|
|
|
364
|
-
function generateTsConfig(): string {
|
|
439
|
+
function generateTsConfig(options: InitOptions): string {
|
|
440
|
+
const isESM = options.moduleSystem === 'esm';
|
|
441
|
+
|
|
365
442
|
return JSON.stringify(
|
|
366
443
|
{
|
|
367
444
|
compilerOptions: {
|
|
368
445
|
target: 'ES2022',
|
|
369
|
-
module: 'NodeNext',
|
|
370
|
-
moduleResolution: 'NodeNext',
|
|
446
|
+
module: isESM ? 'NodeNext' : 'CommonJS',
|
|
447
|
+
moduleResolution: isESM ? 'NodeNext' : 'Node',
|
|
371
448
|
lib: ['ES2022'],
|
|
372
449
|
outDir: './dist',
|
|
373
450
|
rootDir: './src',
|
|
@@ -387,12 +464,14 @@ function generateTsConfig(): string {
|
|
|
387
464
|
);
|
|
388
465
|
}
|
|
389
466
|
|
|
390
|
-
function generateJsConfig(): string {
|
|
467
|
+
function generateJsConfig(options: InitOptions): string {
|
|
468
|
+
const isESM = options.moduleSystem === 'esm';
|
|
469
|
+
|
|
391
470
|
return JSON.stringify(
|
|
392
471
|
{
|
|
393
472
|
compilerOptions: {
|
|
394
|
-
module: 'NodeNext',
|
|
395
|
-
moduleResolution: 'NodeNext',
|
|
473
|
+
module: isESM ? 'NodeNext' : 'CommonJS',
|
|
474
|
+
moduleResolution: isESM ? 'NodeNext' : 'Node',
|
|
396
475
|
target: 'ES2022',
|
|
397
476
|
checkJs: true,
|
|
398
477
|
},
|
|
@@ -404,12 +483,14 @@ function generateJsConfig(): string {
|
|
|
404
483
|
);
|
|
405
484
|
}
|
|
406
485
|
|
|
407
|
-
function generateTsupConfig(): string {
|
|
486
|
+
function generateTsupConfig(options: InitOptions): string {
|
|
487
|
+
const isESM = options.moduleSystem === 'esm';
|
|
488
|
+
|
|
408
489
|
return `import { defineConfig } from 'tsup';
|
|
409
490
|
|
|
410
491
|
export default defineConfig({
|
|
411
492
|
entry: ['src/index.ts'],
|
|
412
|
-
format: ['esm'],
|
|
493
|
+
format: ['${isESM ? 'esm' : 'cjs'}'],
|
|
413
494
|
dts: true,
|
|
414
495
|
clean: true,
|
|
415
496
|
sourcemap: true,
|
|
@@ -619,8 +700,13 @@ model User {
|
|
|
619
700
|
|
|
620
701
|
function generateEntryFile(options: InitOptions): string {
|
|
621
702
|
const isTS = options.language === 'typescript';
|
|
703
|
+
const isESM = options.moduleSystem === 'esm';
|
|
704
|
+
const fileExt = isTS ? 'js' : isESM ? 'js' : 'cjs';
|
|
622
705
|
|
|
623
|
-
|
|
706
|
+
if (isESM || isTS) {
|
|
707
|
+
return `import 'dotenv/config';
|
|
708
|
+
import { createServer } from './core/server.${fileExt}';
|
|
709
|
+
import { logger } from './core/logger.${fileExt}';
|
|
624
710
|
|
|
625
711
|
async function main()${isTS ? ': Promise<void>' : ''} {
|
|
626
712
|
const server = createServer();
|
|
@@ -635,22 +721,34 @@ async function main()${isTS ? ': Promise<void>' : ''} {
|
|
|
635
721
|
|
|
636
722
|
main();
|
|
637
723
|
`;
|
|
724
|
+
} else {
|
|
725
|
+
// CommonJS
|
|
726
|
+
return `require('dotenv').config();
|
|
727
|
+
const { createServer } = require('./core/server.cjs');
|
|
728
|
+
const { logger } = require('./core/logger.cjs');
|
|
729
|
+
|
|
730
|
+
async function main() {
|
|
731
|
+
const server = createServer();
|
|
732
|
+
|
|
733
|
+
try {
|
|
734
|
+
await server.start();
|
|
735
|
+
} catch (error) {
|
|
736
|
+
logger.error({ err: error }, 'Failed to start server');
|
|
737
|
+
process.exit(1);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
main();
|
|
742
|
+
`;
|
|
743
|
+
}
|
|
638
744
|
}
|
|
639
745
|
|
|
640
746
|
function generateServerFile(options: InitOptions): string {
|
|
641
747
|
const isTS = options.language === 'typescript';
|
|
748
|
+
const isESM = options.moduleSystem === 'esm';
|
|
749
|
+
const fileExt = isTS ? 'js' : isESM ? 'js' : 'cjs';
|
|
642
750
|
|
|
643
|
-
|
|
644
|
-
isTS
|
|
645
|
-
? `import Fastify from 'fastify';
|
|
646
|
-
import type { FastifyInstance } from 'fastify';
|
|
647
|
-
import { logger } from './logger.js';`
|
|
648
|
-
: `const Fastify = require('fastify');
|
|
649
|
-
const { logger } = require('./logger.js');`
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
${isTS ? 'export function createServer(): { instance: FastifyInstance; start: () => Promise<void> }' : 'function createServer()'} {
|
|
653
|
-
const app = Fastify({ logger });
|
|
751
|
+
const serverBody = ` const app = Fastify({ logger });
|
|
654
752
|
|
|
655
753
|
// Health check
|
|
656
754
|
app.get('/health', async () => ({
|
|
@@ -677,37 +775,66 @@ ${isTS ? 'export function createServer(): { instance: FastifyInstance; start: ()
|
|
|
677
775
|
logger.info(\`Server listening on \${host}:\${port}\`);
|
|
678
776
|
},
|
|
679
777
|
};
|
|
680
|
-
}
|
|
778
|
+
}`;
|
|
779
|
+
|
|
780
|
+
if (isESM || isTS) {
|
|
781
|
+
return `import Fastify from 'fastify';
|
|
782
|
+
${isTS ? "import type { FastifyInstance } from 'fastify';" : ''}
|
|
783
|
+
import { logger } from './logger.${fileExt}';
|
|
784
|
+
|
|
785
|
+
${isTS ? 'export function createServer(): { instance: FastifyInstance; start: () => Promise<void> }' : 'export function createServer()'} {
|
|
786
|
+
${serverBody}
|
|
787
|
+
`;
|
|
788
|
+
} else {
|
|
789
|
+
// CommonJS
|
|
790
|
+
return `const Fastify = require('fastify');
|
|
791
|
+
const { logger } = require('./logger.cjs');
|
|
792
|
+
|
|
793
|
+
function createServer() {
|
|
794
|
+
${serverBody}
|
|
681
795
|
|
|
682
|
-
|
|
796
|
+
module.exports = { createServer };
|
|
683
797
|
`;
|
|
798
|
+
}
|
|
684
799
|
}
|
|
685
800
|
|
|
686
801
|
function generateLoggerFile(options: InitOptions): string {
|
|
687
802
|
const isTS = options.language === 'typescript';
|
|
803
|
+
const isESM = options.moduleSystem === 'esm';
|
|
688
804
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
${isTS ? 'export const logger: Logger' : 'const logger'} = pino({
|
|
805
|
+
const loggerBody = `pino({
|
|
692
806
|
level: process.env.LOG_LEVEL || 'info',
|
|
693
807
|
transport: process.env.NODE_ENV !== 'production' ? {
|
|
694
808
|
target: 'pino-pretty',
|
|
695
809
|
options: { colorize: true },
|
|
696
810
|
} : undefined,
|
|
697
|
-
})
|
|
811
|
+
})`;
|
|
698
812
|
|
|
699
|
-
|
|
813
|
+
if (isESM || isTS) {
|
|
814
|
+
return `import pino from 'pino';
|
|
815
|
+
${isTS ? "import type { Logger } from 'pino';" : ''}
|
|
816
|
+
|
|
817
|
+
export const logger${isTS ? ': Logger' : ''} = ${loggerBody};
|
|
700
818
|
`;
|
|
819
|
+
} else {
|
|
820
|
+
// CommonJS
|
|
821
|
+
return `const pino = require('pino');
|
|
822
|
+
|
|
823
|
+
const logger = ${loggerBody};
|
|
824
|
+
|
|
825
|
+
module.exports = { logger };
|
|
826
|
+
`;
|
|
827
|
+
}
|
|
701
828
|
}
|
|
702
829
|
|
|
703
830
|
function generateMongooseConnection(options: InitOptions): string {
|
|
704
831
|
const isTS = options.language === 'typescript';
|
|
832
|
+
const isESM = options.moduleSystem === 'esm';
|
|
833
|
+
const fileExt = isTS ? 'js' : isESM ? 'js' : 'cjs';
|
|
705
834
|
|
|
706
|
-
|
|
835
|
+
const connectionBody = `const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/mydb';
|
|
707
836
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
${isTS ? 'export async function connectDatabase(): Promise<typeof mongoose>' : 'async function connectDatabase()'} {
|
|
837
|
+
async function connectDatabase()${isTS ? ': Promise<typeof mongoose>' : ''} {
|
|
711
838
|
try {
|
|
712
839
|
const conn = await mongoose.connect(MONGODB_URI);
|
|
713
840
|
logger.info(\`MongoDB connected: \${conn.connection.host}\`);
|
|
@@ -718,41 +845,40 @@ ${isTS ? 'export async function connectDatabase(): Promise<typeof mongoose>' : '
|
|
|
718
845
|
}
|
|
719
846
|
}
|
|
720
847
|
|
|
721
|
-
${isTS ? '
|
|
848
|
+
async function disconnectDatabase()${isTS ? ': Promise<void>' : ''} {
|
|
722
849
|
try {
|
|
723
850
|
await mongoose.disconnect();
|
|
724
851
|
logger.info('MongoDB disconnected');
|
|
725
852
|
} catch (error) {
|
|
726
853
|
logger.error({ err: error }, 'MongoDB disconnect failed');
|
|
727
854
|
}
|
|
728
|
-
}
|
|
855
|
+
}`;
|
|
856
|
+
|
|
857
|
+
if (isESM || isTS) {
|
|
858
|
+
return `import mongoose from 'mongoose';
|
|
859
|
+
import { logger } from '../core/logger.${fileExt}';
|
|
860
|
+
|
|
861
|
+
${connectionBody}
|
|
862
|
+
|
|
863
|
+
export { connectDatabase, disconnectDatabase, mongoose };
|
|
864
|
+
`;
|
|
865
|
+
} else {
|
|
866
|
+
// CommonJS
|
|
867
|
+
return `const mongoose = require('mongoose');
|
|
868
|
+
const { logger } = require('../core/logger.cjs');
|
|
869
|
+
|
|
870
|
+
${connectionBody}
|
|
729
871
|
|
|
730
|
-
|
|
872
|
+
module.exports = { connectDatabase, disconnectDatabase, mongoose };
|
|
731
873
|
`;
|
|
874
|
+
}
|
|
732
875
|
}
|
|
733
876
|
|
|
734
877
|
function generateMongooseUserModel(options: InitOptions): string {
|
|
735
878
|
const isTS = options.language === 'typescript';
|
|
879
|
+
const isESM = options.moduleSystem === 'esm';
|
|
736
880
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
${
|
|
740
|
-
isTS
|
|
741
|
-
? `export interface IUser extends Document {
|
|
742
|
-
email: string;
|
|
743
|
-
password: string;
|
|
744
|
-
name?: string;
|
|
745
|
-
role: 'user' | 'admin';
|
|
746
|
-
status: 'active' | 'inactive' | 'suspended';
|
|
747
|
-
emailVerified: boolean;
|
|
748
|
-
createdAt: Date;
|
|
749
|
-
updatedAt: Date;
|
|
750
|
-
comparePassword(candidatePassword: string): Promise<boolean>;
|
|
751
|
-
}`
|
|
752
|
-
: ''
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
const userSchema = new Schema${isTS ? '<IUser>' : ''}({
|
|
881
|
+
const schemaBody = `const userSchema = new Schema${isTS ? '<IUser>' : ''}({
|
|
756
882
|
email: {
|
|
757
883
|
type: String,
|
|
758
884
|
required: true,
|
|
@@ -803,8 +929,258 @@ userSchema.pre('save', async function(next) {
|
|
|
803
929
|
// Compare password method
|
|
804
930
|
userSchema.methods.comparePassword = async function(candidatePassword${isTS ? ': string' : ''})${isTS ? ': Promise<boolean>' : ''} {
|
|
805
931
|
return bcrypt.compare(candidatePassword, this.password);
|
|
806
|
-
}
|
|
932
|
+
};`;
|
|
933
|
+
|
|
934
|
+
const tsInterface = isTS
|
|
935
|
+
? `
|
|
936
|
+
export interface IUser extends Document {
|
|
937
|
+
email: string;
|
|
938
|
+
password: string;
|
|
939
|
+
name?: string;
|
|
940
|
+
role: 'user' | 'admin';
|
|
941
|
+
status: 'active' | 'inactive' | 'suspended';
|
|
942
|
+
emailVerified: boolean;
|
|
943
|
+
createdAt: Date;
|
|
944
|
+
updatedAt: Date;
|
|
945
|
+
comparePassword(candidatePassword: string): Promise<boolean>;
|
|
946
|
+
}
|
|
947
|
+
`
|
|
948
|
+
: '';
|
|
949
|
+
|
|
950
|
+
if (isESM || isTS) {
|
|
951
|
+
return `import mongoose${isTS ? ', { Schema, Document }' : ''} from 'mongoose';
|
|
952
|
+
import bcrypt from 'bcryptjs';
|
|
953
|
+
${!isTS ? 'const { Schema } = mongoose;' : ''}
|
|
954
|
+
${tsInterface}
|
|
955
|
+
${schemaBody}
|
|
956
|
+
|
|
957
|
+
export const User = mongoose.model${isTS ? '<IUser>' : ''}('User', userSchema);
|
|
958
|
+
`;
|
|
959
|
+
} else {
|
|
960
|
+
// CommonJS
|
|
961
|
+
return `const mongoose = require('mongoose');
|
|
962
|
+
const bcrypt = require('bcryptjs');
|
|
963
|
+
const { Schema } = mongoose;
|
|
964
|
+
|
|
965
|
+
${schemaBody}
|
|
966
|
+
|
|
967
|
+
const User = mongoose.model('User', userSchema);
|
|
968
|
+
|
|
969
|
+
module.exports = { User };
|
|
970
|
+
`;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
function generateConfigFile(options: InitOptions): string {
|
|
975
|
+
const isTS = options.language === 'typescript';
|
|
976
|
+
const isESM = options.moduleSystem === 'esm';
|
|
977
|
+
|
|
978
|
+
const configBody = `{
|
|
979
|
+
env: process.env.NODE_ENV || 'development',
|
|
980
|
+
port: parseInt(process.env.PORT || '3000', 10),
|
|
981
|
+
host: process.env.HOST || '0.0.0.0',
|
|
982
|
+
|
|
983
|
+
jwt: {
|
|
984
|
+
secret: process.env.JWT_SECRET || 'your-secret-key',
|
|
985
|
+
accessExpiresIn: process.env.JWT_ACCESS_EXPIRES_IN || '15m',
|
|
986
|
+
refreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN || '7d',
|
|
987
|
+
},
|
|
988
|
+
|
|
989
|
+
cors: {
|
|
990
|
+
origin: process.env.CORS_ORIGIN || 'http://localhost:3000',
|
|
991
|
+
},
|
|
992
|
+
|
|
993
|
+
rateLimit: {
|
|
994
|
+
max: parseInt(process.env.RATE_LIMIT_MAX || '100', 10),
|
|
995
|
+
},
|
|
996
|
+
|
|
997
|
+
log: {
|
|
998
|
+
level: process.env.LOG_LEVEL || 'info',
|
|
999
|
+
},
|
|
1000
|
+
}${isTS ? ' as const' : ''}`;
|
|
1001
|
+
|
|
1002
|
+
if (isESM || isTS) {
|
|
1003
|
+
return `import 'dotenv/config';
|
|
1004
|
+
|
|
1005
|
+
export const config = ${configBody};
|
|
1006
|
+
`;
|
|
1007
|
+
} else {
|
|
1008
|
+
// CommonJS
|
|
1009
|
+
return `require('dotenv').config();
|
|
1010
|
+
|
|
1011
|
+
const config = ${configBody};
|
|
1012
|
+
|
|
1013
|
+
module.exports = { config };
|
|
1014
|
+
`;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
function generateMiddlewareFile(options: InitOptions): string {
|
|
1019
|
+
const isTS = options.language === 'typescript';
|
|
1020
|
+
const isESM = options.moduleSystem === 'esm';
|
|
1021
|
+
const fileExt = isTS ? 'js' : isESM ? 'js' : 'cjs';
|
|
1022
|
+
|
|
1023
|
+
const middlewareBody = `/**
|
|
1024
|
+
* Error handler middleware
|
|
1025
|
+
*/
|
|
1026
|
+
function errorHandler(error${isTS ? ': Error' : ''}, request${isTS ? ': FastifyRequest' : ''}, reply${isTS ? ': FastifyReply' : ''})${isTS ? ': void' : ''} {
|
|
1027
|
+
logger.error({ err: error, url: request.url, method: request.method }, 'Request error');
|
|
1028
|
+
|
|
1029
|
+
const statusCode = (error${isTS ? ' as any' : ''}).statusCode || 500;
|
|
1030
|
+
const message = statusCode === 500 ? 'Internal Server Error' : error.message;
|
|
1031
|
+
|
|
1032
|
+
reply.status(statusCode).send({
|
|
1033
|
+
success: false,
|
|
1034
|
+
error: message,
|
|
1035
|
+
...(process.env.NODE_ENV === 'development' && { stack: error.stack }),
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* Request logging middleware
|
|
1041
|
+
*/
|
|
1042
|
+
function requestLogger(request${isTS ? ': FastifyRequest' : ''}, reply${isTS ? ': FastifyReply' : ''}, done${isTS ? ': () => void' : ''})${isTS ? ': void' : ''} {
|
|
1043
|
+
logger.info({ url: request.url, method: request.method, ip: request.ip }, 'Incoming request');
|
|
1044
|
+
done();
|
|
1045
|
+
}`;
|
|
1046
|
+
|
|
1047
|
+
if (isESM || isTS) {
|
|
1048
|
+
return `${isTS ? "import type { FastifyRequest, FastifyReply } from 'fastify';" : ''}
|
|
1049
|
+
import { logger } from '../core/logger.${fileExt}';
|
|
1050
|
+
|
|
1051
|
+
${middlewareBody}
|
|
1052
|
+
|
|
1053
|
+
export { errorHandler, requestLogger };
|
|
1054
|
+
`;
|
|
1055
|
+
} else {
|
|
1056
|
+
// CommonJS
|
|
1057
|
+
return `const { logger } = require('../core/logger.cjs');
|
|
1058
|
+
|
|
1059
|
+
${middlewareBody}
|
|
807
1060
|
|
|
808
|
-
|
|
1061
|
+
module.exports = { errorHandler, requestLogger };
|
|
1062
|
+
`;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
function generateUtilsFile(options: InitOptions): string {
|
|
1067
|
+
const isTS = options.language === 'typescript';
|
|
1068
|
+
const isESM = options.moduleSystem === 'esm';
|
|
1069
|
+
|
|
1070
|
+
const utilsBody = `/**
|
|
1071
|
+
* Standard API response helper
|
|
1072
|
+
*/
|
|
1073
|
+
function apiResponse${isTS ? '<T>' : ''}(data${isTS ? ': T' : ''}, message = "Success")${isTS ? ': { success: boolean; message: string; data: T }' : ''} {
|
|
1074
|
+
return {
|
|
1075
|
+
success: true,
|
|
1076
|
+
message,
|
|
1077
|
+
data,
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* Error response helper
|
|
1083
|
+
*/
|
|
1084
|
+
function errorResponse(message${isTS ? ': string' : ''}, code${isTS ? '?: string' : ''})${isTS ? ': { success: boolean; error: string; code?: string }' : ''} {
|
|
1085
|
+
return {
|
|
1086
|
+
success: false,
|
|
1087
|
+
error: message,
|
|
1088
|
+
...(code && { code }),
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/**
|
|
1093
|
+
* Pagination helper
|
|
1094
|
+
*/
|
|
1095
|
+
function paginate${isTS ? '<T>' : ''}(data${isTS ? ': T[]' : ''}, page${isTS ? ': number' : ''}, limit${isTS ? ': number' : ''}, total${isTS ? ': number' : ''})${isTS ? ': PaginationResult<T>' : ''} {
|
|
1096
|
+
const totalPages = Math.ceil(total / limit);
|
|
1097
|
+
|
|
1098
|
+
return {
|
|
1099
|
+
data,
|
|
1100
|
+
pagination: {
|
|
1101
|
+
page,
|
|
1102
|
+
limit,
|
|
1103
|
+
total,
|
|
1104
|
+
totalPages,
|
|
1105
|
+
hasNextPage: page < totalPages,
|
|
1106
|
+
hasPrevPage: page > 1,
|
|
1107
|
+
},
|
|
1108
|
+
};
|
|
1109
|
+
}`;
|
|
1110
|
+
|
|
1111
|
+
const tsInterface = isTS
|
|
1112
|
+
? `
|
|
1113
|
+
/**
|
|
1114
|
+
* Pagination result type
|
|
1115
|
+
*/
|
|
1116
|
+
export interface PaginationResult<T> {
|
|
1117
|
+
data: T[];
|
|
1118
|
+
pagination: {
|
|
1119
|
+
page: number;
|
|
1120
|
+
limit: number;
|
|
1121
|
+
total: number;
|
|
1122
|
+
totalPages: number;
|
|
1123
|
+
hasNextPage: boolean;
|
|
1124
|
+
hasPrevPage: boolean;
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
`
|
|
1128
|
+
: '';
|
|
1129
|
+
|
|
1130
|
+
if (isESM || isTS) {
|
|
1131
|
+
return `${tsInterface}
|
|
1132
|
+
${utilsBody}
|
|
1133
|
+
|
|
1134
|
+
export { apiResponse, errorResponse, paginate };
|
|
1135
|
+
`;
|
|
1136
|
+
} else {
|
|
1137
|
+
// CommonJS
|
|
1138
|
+
return `${utilsBody}
|
|
1139
|
+
|
|
1140
|
+
module.exports = { apiResponse, errorResponse, paginate };
|
|
1141
|
+
`;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
function generateTypesFile(options: InitOptions): string {
|
|
1146
|
+
if (options.language !== 'typescript') {
|
|
1147
|
+
return '// Types file - not needed for JavaScript\n';
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
return `/**
|
|
1151
|
+
* Common type definitions
|
|
1152
|
+
*/
|
|
1153
|
+
|
|
1154
|
+
export interface ApiResponse<T = unknown> {
|
|
1155
|
+
success: boolean;
|
|
1156
|
+
message?: string;
|
|
1157
|
+
data?: T;
|
|
1158
|
+
error?: string;
|
|
1159
|
+
code?: string;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
export interface PaginatedResponse<T> {
|
|
1163
|
+
data: T[];
|
|
1164
|
+
pagination: {
|
|
1165
|
+
page: number;
|
|
1166
|
+
limit: number;
|
|
1167
|
+
total: number;
|
|
1168
|
+
totalPages: number;
|
|
1169
|
+
hasNextPage: boolean;
|
|
1170
|
+
hasPrevPage: boolean;
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
export interface RequestUser {
|
|
1175
|
+
id: string;
|
|
1176
|
+
email: string;
|
|
1177
|
+
role: string;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
declare module 'fastify' {
|
|
1181
|
+
interface FastifyRequest {
|
|
1182
|
+
user?: RequestUser;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
809
1185
|
`;
|
|
810
1186
|
}
|
package/src/cli/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { initCommand } from './commands/init.js';
|
|
|
5
5
|
import { generateCommand } from './commands/generate.js';
|
|
6
6
|
import { addModuleCommand } from './commands/add-module.js';
|
|
7
7
|
import { dbCommand } from './commands/db.js';
|
|
8
|
+
import { docsCommand } from './commands/docs.js';
|
|
8
9
|
|
|
9
10
|
const program = new Command();
|
|
10
11
|
|
|
@@ -25,4 +26,7 @@ program.addCommand(addModuleCommand);
|
|
|
25
26
|
// Database commands
|
|
26
27
|
program.addCommand(dbCommand);
|
|
27
28
|
|
|
29
|
+
// Documentation commands
|
|
30
|
+
program.addCommand(docsCommand);
|
|
31
|
+
|
|
28
32
|
program.parse();
|