servcraft 0.1.6 → 0.2.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/README.md +51 -4
- package/ROADMAP.md +327 -0
- package/dist/cli/index.cjs +3039 -222
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +3058 -220
- package/dist/cli/index.js.map +1 -1
- package/package.json +2 -2
- package/src/cli/commands/add-module.ts +37 -36
- package/src/cli/commands/doctor.ts +8 -0
- package/src/cli/commands/generate.ts +43 -1
- package/src/cli/commands/init.ts +470 -75
- package/src/cli/commands/list.ts +274 -0
- package/src/cli/commands/remove.ts +102 -0
- package/src/cli/index.ts +16 -0
- package/src/cli/utils/dry-run.ts +155 -0
- package/src/cli/utils/error-handler.ts +184 -0
- package/src/cli/utils/helpers.ts +13 -0
package/src/cli/commands/init.ts
CHANGED
|
@@ -6,10 +6,12 @@ import inquirer from 'inquirer';
|
|
|
6
6
|
import chalk from 'chalk';
|
|
7
7
|
import { execSync } from 'child_process';
|
|
8
8
|
import { ensureDir, writeFile, error, warn } from '../utils/helpers.js';
|
|
9
|
+
import { DryRunManager } from '../utils/dry-run.js';
|
|
9
10
|
|
|
10
11
|
interface InitOptions {
|
|
11
12
|
name: string;
|
|
12
13
|
language: 'typescript' | 'javascript';
|
|
14
|
+
moduleSystem: 'esm' | 'commonjs';
|
|
13
15
|
database: 'postgresql' | 'mysql' | 'sqlite' | 'mongodb' | 'none';
|
|
14
16
|
orm: 'prisma' | 'mongoose' | 'none';
|
|
15
17
|
validator: 'zod' | 'joi' | 'yup';
|
|
@@ -23,12 +25,30 @@ export const initCommand = new Command('init')
|
|
|
23
25
|
.option('-y, --yes', 'Skip prompts and use defaults')
|
|
24
26
|
.option('--ts, --typescript', 'Use TypeScript (default)')
|
|
25
27
|
.option('--js, --javascript', 'Use JavaScript')
|
|
28
|
+
.option('--esm', 'Use ES Modules (import/export) - default')
|
|
29
|
+
.option('--cjs, --commonjs', 'Use CommonJS (require/module.exports)')
|
|
26
30
|
.option('--db <database>', 'Database type (postgresql, mysql, sqlite, mongodb, none)')
|
|
31
|
+
.option('--dry-run', 'Preview changes without writing files')
|
|
27
32
|
.action(
|
|
28
33
|
async (
|
|
29
34
|
name?: string,
|
|
30
|
-
cmdOptions?: {
|
|
35
|
+
cmdOptions?: {
|
|
36
|
+
yes?: boolean;
|
|
37
|
+
typescript?: boolean;
|
|
38
|
+
javascript?: boolean;
|
|
39
|
+
esm?: boolean;
|
|
40
|
+
commonjs?: boolean;
|
|
41
|
+
db?: string;
|
|
42
|
+
dryRun?: boolean;
|
|
43
|
+
}
|
|
31
44
|
) => {
|
|
45
|
+
// Enable dry-run mode if specified
|
|
46
|
+
const dryRun = DryRunManager.getInstance();
|
|
47
|
+
if (cmdOptions?.dryRun) {
|
|
48
|
+
dryRun.enable();
|
|
49
|
+
console.log(chalk.yellow('\n⚠ DRY RUN MODE - No files will be written\n'));
|
|
50
|
+
}
|
|
51
|
+
|
|
32
52
|
console.log(
|
|
33
53
|
chalk.blue(`
|
|
34
54
|
╔═══════════════════════════════════════════╗
|
|
@@ -46,6 +66,7 @@ export const initCommand = new Command('init')
|
|
|
46
66
|
options = {
|
|
47
67
|
name: name || 'my-servcraft-app',
|
|
48
68
|
language: cmdOptions.javascript ? 'javascript' : 'typescript',
|
|
69
|
+
moduleSystem: cmdOptions.commonjs ? 'commonjs' : 'esm',
|
|
49
70
|
database: db,
|
|
50
71
|
orm: db === 'mongodb' ? 'mongoose' : db === 'none' ? 'none' : 'prisma',
|
|
51
72
|
validator: 'zod',
|
|
@@ -75,6 +96,16 @@ export const initCommand = new Command('init')
|
|
|
75
96
|
],
|
|
76
97
|
default: 'typescript',
|
|
77
98
|
},
|
|
99
|
+
{
|
|
100
|
+
type: 'list',
|
|
101
|
+
name: 'moduleSystem',
|
|
102
|
+
message: 'Select module system:',
|
|
103
|
+
choices: [
|
|
104
|
+
{ name: 'ESM (import/export) - Recommended', value: 'esm' },
|
|
105
|
+
{ name: 'CommonJS (require/module.exports)', value: 'commonjs' },
|
|
106
|
+
],
|
|
107
|
+
default: 'esm',
|
|
108
|
+
},
|
|
78
109
|
{
|
|
79
110
|
type: 'list',
|
|
80
111
|
name: 'database',
|
|
@@ -150,10 +181,10 @@ export const initCommand = new Command('init')
|
|
|
150
181
|
|
|
151
182
|
// Generate tsconfig or jsconfig
|
|
152
183
|
if (options.language === 'typescript') {
|
|
153
|
-
await writeFile(path.join(projectDir, 'tsconfig.json'), generateTsConfig());
|
|
154
|
-
await writeFile(path.join(projectDir, 'tsup.config.ts'), generateTsupConfig());
|
|
184
|
+
await writeFile(path.join(projectDir, 'tsconfig.json'), generateTsConfig(options));
|
|
185
|
+
await writeFile(path.join(projectDir, 'tsup.config.ts'), generateTsupConfig(options));
|
|
155
186
|
} else {
|
|
156
|
-
await writeFile(path.join(projectDir, 'jsconfig.json'), generateJsConfig());
|
|
187
|
+
await writeFile(path.join(projectDir, 'jsconfig.json'), generateJsConfig(options));
|
|
157
188
|
}
|
|
158
189
|
|
|
159
190
|
// Generate .env files
|
|
@@ -171,7 +202,10 @@ export const initCommand = new Command('init')
|
|
|
171
202
|
);
|
|
172
203
|
|
|
173
204
|
// Create directory structure
|
|
174
|
-
|
|
205
|
+
// For JS: .js for ESM, .cjs for CommonJS
|
|
206
|
+
// For TS: always .ts (output format handled by tsup)
|
|
207
|
+
const ext =
|
|
208
|
+
options.language === 'typescript' ? 'ts' : options.moduleSystem === 'esm' ? 'js' : 'cjs';
|
|
175
209
|
const dirs = [
|
|
176
210
|
'src/core',
|
|
177
211
|
'src/config',
|
|
@@ -207,6 +241,30 @@ export const initCommand = new Command('init')
|
|
|
207
241
|
generateLoggerFile(options)
|
|
208
242
|
);
|
|
209
243
|
|
|
244
|
+
// Generate config file
|
|
245
|
+
await writeFile(
|
|
246
|
+
path.join(projectDir, `src/config/index.${ext}`),
|
|
247
|
+
generateConfigFile(options)
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
// Generate middleware file
|
|
251
|
+
await writeFile(
|
|
252
|
+
path.join(projectDir, `src/middleware/index.${ext}`),
|
|
253
|
+
generateMiddlewareFile(options)
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
// Generate utils file
|
|
257
|
+
await writeFile(
|
|
258
|
+
path.join(projectDir, `src/utils/index.${ext}`),
|
|
259
|
+
generateUtilsFile(options)
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
// Generate types file
|
|
263
|
+
await writeFile(
|
|
264
|
+
path.join(projectDir, `src/types/index.${ext}`),
|
|
265
|
+
generateTypesFile(options)
|
|
266
|
+
);
|
|
267
|
+
|
|
210
268
|
// Generate database files based on ORM choice
|
|
211
269
|
if (options.orm === 'prisma') {
|
|
212
270
|
await writeFile(
|
|
@@ -226,19 +284,23 @@ export const initCommand = new Command('init')
|
|
|
226
284
|
|
|
227
285
|
spinner.succeed('Project files generated!');
|
|
228
286
|
|
|
229
|
-
// Install dependencies
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
287
|
+
// Install dependencies (skip in dry-run mode)
|
|
288
|
+
if (!cmdOptions?.dryRun) {
|
|
289
|
+
const installSpinner = ora('Installing dependencies...').start();
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
execSync('npm install', { cwd: projectDir, stdio: 'pipe' });
|
|
293
|
+
installSpinner.succeed('Dependencies installed!');
|
|
294
|
+
} catch {
|
|
295
|
+
installSpinner.warn('Failed to install dependencies automatically');
|
|
296
|
+
warn(' Run "npm install" manually in the project directory');
|
|
297
|
+
}
|
|
238
298
|
}
|
|
239
299
|
|
|
240
300
|
// Print success message
|
|
241
|
-
|
|
301
|
+
if (!cmdOptions?.dryRun) {
|
|
302
|
+
console.log('\n' + chalk.green('✨ Project created successfully!'));
|
|
303
|
+
}
|
|
242
304
|
console.log('\n' + chalk.bold('📁 Project structure:'));
|
|
243
305
|
console.log(`
|
|
244
306
|
${options.name}/
|
|
@@ -269,6 +331,11 @@ export const initCommand = new Command('init')
|
|
|
269
331
|
${chalk.yellow('servcraft generate service <name>')} Generate a service
|
|
270
332
|
${chalk.yellow('servcraft add auth')} Add authentication module
|
|
271
333
|
`);
|
|
334
|
+
|
|
335
|
+
// Show dry-run summary if enabled
|
|
336
|
+
if (cmdOptions?.dryRun) {
|
|
337
|
+
dryRun.printSummary();
|
|
338
|
+
}
|
|
272
339
|
} catch (err) {
|
|
273
340
|
spinner.fail('Failed to create project');
|
|
274
341
|
error(err instanceof Error ? err.message : String(err));
|
|
@@ -278,19 +345,46 @@ export const initCommand = new Command('init')
|
|
|
278
345
|
|
|
279
346
|
function generatePackageJson(options: InitOptions): Record<string, unknown> {
|
|
280
347
|
const isTS = options.language === 'typescript';
|
|
348
|
+
const isESM = options.moduleSystem === 'esm';
|
|
349
|
+
|
|
350
|
+
// Determine dev command based on language and module system
|
|
351
|
+
let devCommand: string;
|
|
352
|
+
if (isTS) {
|
|
353
|
+
devCommand = 'tsx watch src/index.ts';
|
|
354
|
+
} else if (isESM) {
|
|
355
|
+
devCommand = 'node --watch src/index.js';
|
|
356
|
+
} else {
|
|
357
|
+
devCommand = 'node --watch src/index.cjs';
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Determine start command
|
|
361
|
+
let startCommand: string;
|
|
362
|
+
if (isTS) {
|
|
363
|
+
startCommand = isESM ? 'node dist/index.js' : 'node dist/index.cjs';
|
|
364
|
+
} else if (isESM) {
|
|
365
|
+
startCommand = 'node src/index.js';
|
|
366
|
+
} else {
|
|
367
|
+
startCommand = 'node src/index.cjs';
|
|
368
|
+
}
|
|
281
369
|
|
|
282
370
|
const pkg: Record<string, unknown> = {
|
|
283
371
|
name: options.name,
|
|
284
372
|
version: '0.1.0',
|
|
285
373
|
description: 'A Servcraft application',
|
|
286
|
-
main: isTS
|
|
287
|
-
|
|
374
|
+
main: isTS
|
|
375
|
+
? isESM
|
|
376
|
+
? 'dist/index.js'
|
|
377
|
+
: 'dist/index.cjs'
|
|
378
|
+
: isESM
|
|
379
|
+
? 'src/index.js'
|
|
380
|
+
: 'src/index.cjs',
|
|
381
|
+
...(isESM && { type: 'module' }),
|
|
288
382
|
scripts: {
|
|
289
|
-
dev:
|
|
383
|
+
dev: devCommand,
|
|
290
384
|
build: isTS ? 'tsup' : 'echo "No build needed for JS"',
|
|
291
|
-
start:
|
|
385
|
+
start: startCommand,
|
|
292
386
|
test: 'vitest',
|
|
293
|
-
lint: isTS ? 'eslint src --ext .ts' : 'eslint src --ext .js',
|
|
387
|
+
lint: isTS ? 'eslint src --ext .ts' : 'eslint src --ext .js,.cjs',
|
|
294
388
|
},
|
|
295
389
|
dependencies: {
|
|
296
390
|
fastify: '^4.28.1',
|
|
@@ -361,13 +455,15 @@ function generatePackageJson(options: InitOptions): Record<string, unknown> {
|
|
|
361
455
|
return pkg;
|
|
362
456
|
}
|
|
363
457
|
|
|
364
|
-
function generateTsConfig(): string {
|
|
458
|
+
function generateTsConfig(options: InitOptions): string {
|
|
459
|
+
const isESM = options.moduleSystem === 'esm';
|
|
460
|
+
|
|
365
461
|
return JSON.stringify(
|
|
366
462
|
{
|
|
367
463
|
compilerOptions: {
|
|
368
464
|
target: 'ES2022',
|
|
369
|
-
module: 'NodeNext',
|
|
370
|
-
moduleResolution: 'NodeNext',
|
|
465
|
+
module: isESM ? 'NodeNext' : 'CommonJS',
|
|
466
|
+
moduleResolution: isESM ? 'NodeNext' : 'Node',
|
|
371
467
|
lib: ['ES2022'],
|
|
372
468
|
outDir: './dist',
|
|
373
469
|
rootDir: './src',
|
|
@@ -387,12 +483,14 @@ function generateTsConfig(): string {
|
|
|
387
483
|
);
|
|
388
484
|
}
|
|
389
485
|
|
|
390
|
-
function generateJsConfig(): string {
|
|
486
|
+
function generateJsConfig(options: InitOptions): string {
|
|
487
|
+
const isESM = options.moduleSystem === 'esm';
|
|
488
|
+
|
|
391
489
|
return JSON.stringify(
|
|
392
490
|
{
|
|
393
491
|
compilerOptions: {
|
|
394
|
-
module: 'NodeNext',
|
|
395
|
-
moduleResolution: 'NodeNext',
|
|
492
|
+
module: isESM ? 'NodeNext' : 'CommonJS',
|
|
493
|
+
moduleResolution: isESM ? 'NodeNext' : 'Node',
|
|
396
494
|
target: 'ES2022',
|
|
397
495
|
checkJs: true,
|
|
398
496
|
},
|
|
@@ -404,12 +502,14 @@ function generateJsConfig(): string {
|
|
|
404
502
|
);
|
|
405
503
|
}
|
|
406
504
|
|
|
407
|
-
function generateTsupConfig(): string {
|
|
505
|
+
function generateTsupConfig(options: InitOptions): string {
|
|
506
|
+
const isESM = options.moduleSystem === 'esm';
|
|
507
|
+
|
|
408
508
|
return `import { defineConfig } from 'tsup';
|
|
409
509
|
|
|
410
510
|
export default defineConfig({
|
|
411
511
|
entry: ['src/index.ts'],
|
|
412
|
-
format: ['esm'],
|
|
512
|
+
format: ['${isESM ? 'esm' : 'cjs'}'],
|
|
413
513
|
dts: true,
|
|
414
514
|
clean: true,
|
|
415
515
|
sourcemap: true,
|
|
@@ -619,8 +719,13 @@ model User {
|
|
|
619
719
|
|
|
620
720
|
function generateEntryFile(options: InitOptions): string {
|
|
621
721
|
const isTS = options.language === 'typescript';
|
|
722
|
+
const isESM = options.moduleSystem === 'esm';
|
|
723
|
+
const fileExt = isTS ? 'js' : isESM ? 'js' : 'cjs';
|
|
622
724
|
|
|
623
|
-
|
|
725
|
+
if (isESM || isTS) {
|
|
726
|
+
return `import 'dotenv/config';
|
|
727
|
+
import { createServer } from './core/server.${fileExt}';
|
|
728
|
+
import { logger } from './core/logger.${fileExt}';
|
|
624
729
|
|
|
625
730
|
async function main()${isTS ? ': Promise<void>' : ''} {
|
|
626
731
|
const server = createServer();
|
|
@@ -635,22 +740,34 @@ async function main()${isTS ? ': Promise<void>' : ''} {
|
|
|
635
740
|
|
|
636
741
|
main();
|
|
637
742
|
`;
|
|
743
|
+
} else {
|
|
744
|
+
// CommonJS
|
|
745
|
+
return `require('dotenv').config();
|
|
746
|
+
const { createServer } = require('./core/server.cjs');
|
|
747
|
+
const { logger } = require('./core/logger.cjs');
|
|
748
|
+
|
|
749
|
+
async function main() {
|
|
750
|
+
const server = createServer();
|
|
751
|
+
|
|
752
|
+
try {
|
|
753
|
+
await server.start();
|
|
754
|
+
} catch (error) {
|
|
755
|
+
logger.error({ err: error }, 'Failed to start server');
|
|
756
|
+
process.exit(1);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
main();
|
|
761
|
+
`;
|
|
762
|
+
}
|
|
638
763
|
}
|
|
639
764
|
|
|
640
765
|
function generateServerFile(options: InitOptions): string {
|
|
641
766
|
const isTS = options.language === 'typescript';
|
|
767
|
+
const isESM = options.moduleSystem === 'esm';
|
|
768
|
+
const fileExt = isTS ? 'js' : isESM ? 'js' : 'cjs';
|
|
642
769
|
|
|
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 });
|
|
770
|
+
const serverBody = ` const app = Fastify({ logger });
|
|
654
771
|
|
|
655
772
|
// Health check
|
|
656
773
|
app.get('/health', async () => ({
|
|
@@ -677,37 +794,66 @@ ${isTS ? 'export function createServer(): { instance: FastifyInstance; start: ()
|
|
|
677
794
|
logger.info(\`Server listening on \${host}:\${port}\`);
|
|
678
795
|
},
|
|
679
796
|
};
|
|
680
|
-
}
|
|
797
|
+
}`;
|
|
798
|
+
|
|
799
|
+
if (isESM || isTS) {
|
|
800
|
+
return `import Fastify from 'fastify';
|
|
801
|
+
${isTS ? "import type { FastifyInstance } from 'fastify';" : ''}
|
|
802
|
+
import { logger } from './logger.${fileExt}';
|
|
803
|
+
|
|
804
|
+
${isTS ? 'export function createServer(): { instance: FastifyInstance; start: () => Promise<void> }' : 'export function createServer()'} {
|
|
805
|
+
${serverBody}
|
|
806
|
+
`;
|
|
807
|
+
} else {
|
|
808
|
+
// CommonJS
|
|
809
|
+
return `const Fastify = require('fastify');
|
|
810
|
+
const { logger } = require('./logger.cjs');
|
|
681
811
|
|
|
682
|
-
|
|
812
|
+
function createServer() {
|
|
813
|
+
${serverBody}
|
|
814
|
+
|
|
815
|
+
module.exports = { createServer };
|
|
683
816
|
`;
|
|
817
|
+
}
|
|
684
818
|
}
|
|
685
819
|
|
|
686
820
|
function generateLoggerFile(options: InitOptions): string {
|
|
687
821
|
const isTS = options.language === 'typescript';
|
|
822
|
+
const isESM = options.moduleSystem === 'esm';
|
|
688
823
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
${isTS ? 'export const logger: Logger' : 'const logger'} = pino({
|
|
824
|
+
const loggerBody = `pino({
|
|
692
825
|
level: process.env.LOG_LEVEL || 'info',
|
|
693
826
|
transport: process.env.NODE_ENV !== 'production' ? {
|
|
694
827
|
target: 'pino-pretty',
|
|
695
828
|
options: { colorize: true },
|
|
696
829
|
} : undefined,
|
|
697
|
-
})
|
|
830
|
+
})`;
|
|
831
|
+
|
|
832
|
+
if (isESM || isTS) {
|
|
833
|
+
return `import pino from 'pino';
|
|
834
|
+
${isTS ? "import type { Logger } from 'pino';" : ''}
|
|
698
835
|
|
|
699
|
-
${isTS ? '' : '
|
|
836
|
+
export const logger${isTS ? ': Logger' : ''} = ${loggerBody};
|
|
700
837
|
`;
|
|
838
|
+
} else {
|
|
839
|
+
// CommonJS
|
|
840
|
+
return `const pino = require('pino');
|
|
841
|
+
|
|
842
|
+
const logger = ${loggerBody};
|
|
843
|
+
|
|
844
|
+
module.exports = { logger };
|
|
845
|
+
`;
|
|
846
|
+
}
|
|
701
847
|
}
|
|
702
848
|
|
|
703
849
|
function generateMongooseConnection(options: InitOptions): string {
|
|
704
850
|
const isTS = options.language === 'typescript';
|
|
851
|
+
const isESM = options.moduleSystem === 'esm';
|
|
852
|
+
const fileExt = isTS ? 'js' : isESM ? 'js' : 'cjs';
|
|
705
853
|
|
|
706
|
-
|
|
854
|
+
const connectionBody = `const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/mydb';
|
|
707
855
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
${isTS ? 'export async function connectDatabase(): Promise<typeof mongoose>' : 'async function connectDatabase()'} {
|
|
856
|
+
async function connectDatabase()${isTS ? ': Promise<typeof mongoose>' : ''} {
|
|
711
857
|
try {
|
|
712
858
|
const conn = await mongoose.connect(MONGODB_URI);
|
|
713
859
|
logger.info(\`MongoDB connected: \${conn.connection.host}\`);
|
|
@@ -718,41 +864,40 @@ ${isTS ? 'export async function connectDatabase(): Promise<typeof mongoose>' : '
|
|
|
718
864
|
}
|
|
719
865
|
}
|
|
720
866
|
|
|
721
|
-
${isTS ? '
|
|
867
|
+
async function disconnectDatabase()${isTS ? ': Promise<void>' : ''} {
|
|
722
868
|
try {
|
|
723
869
|
await mongoose.disconnect();
|
|
724
870
|
logger.info('MongoDB disconnected');
|
|
725
871
|
} catch (error) {
|
|
726
872
|
logger.error({ err: error }, 'MongoDB disconnect failed');
|
|
727
873
|
}
|
|
728
|
-
}
|
|
874
|
+
}`;
|
|
729
875
|
|
|
730
|
-
|
|
876
|
+
if (isESM || isTS) {
|
|
877
|
+
return `import mongoose from 'mongoose';
|
|
878
|
+
import { logger } from '../core/logger.${fileExt}';
|
|
879
|
+
|
|
880
|
+
${connectionBody}
|
|
881
|
+
|
|
882
|
+
export { connectDatabase, disconnectDatabase, mongoose };
|
|
883
|
+
`;
|
|
884
|
+
} else {
|
|
885
|
+
// CommonJS
|
|
886
|
+
return `const mongoose = require('mongoose');
|
|
887
|
+
const { logger } = require('../core/logger.cjs');
|
|
888
|
+
|
|
889
|
+
${connectionBody}
|
|
890
|
+
|
|
891
|
+
module.exports = { connectDatabase, disconnectDatabase, mongoose };
|
|
731
892
|
`;
|
|
893
|
+
}
|
|
732
894
|
}
|
|
733
895
|
|
|
734
896
|
function generateMongooseUserModel(options: InitOptions): string {
|
|
735
897
|
const isTS = options.language === 'typescript';
|
|
898
|
+
const isESM = options.moduleSystem === 'esm';
|
|
736
899
|
|
|
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>' : ''}({
|
|
900
|
+
const schemaBody = `const userSchema = new Schema${isTS ? '<IUser>' : ''}({
|
|
756
901
|
email: {
|
|
757
902
|
type: String,
|
|
758
903
|
required: true,
|
|
@@ -803,8 +948,258 @@ userSchema.pre('save', async function(next) {
|
|
|
803
948
|
// Compare password method
|
|
804
949
|
userSchema.methods.comparePassword = async function(candidatePassword${isTS ? ': string' : ''})${isTS ? ': Promise<boolean>' : ''} {
|
|
805
950
|
return bcrypt.compare(candidatePassword, this.password);
|
|
806
|
-
}
|
|
951
|
+
};`;
|
|
952
|
+
|
|
953
|
+
const tsInterface = isTS
|
|
954
|
+
? `
|
|
955
|
+
export interface IUser extends Document {
|
|
956
|
+
email: string;
|
|
957
|
+
password: string;
|
|
958
|
+
name?: string;
|
|
959
|
+
role: 'user' | 'admin';
|
|
960
|
+
status: 'active' | 'inactive' | 'suspended';
|
|
961
|
+
emailVerified: boolean;
|
|
962
|
+
createdAt: Date;
|
|
963
|
+
updatedAt: Date;
|
|
964
|
+
comparePassword(candidatePassword: string): Promise<boolean>;
|
|
965
|
+
}
|
|
966
|
+
`
|
|
967
|
+
: '';
|
|
968
|
+
|
|
969
|
+
if (isESM || isTS) {
|
|
970
|
+
return `import mongoose${isTS ? ', { Schema, Document }' : ''} from 'mongoose';
|
|
971
|
+
import bcrypt from 'bcryptjs';
|
|
972
|
+
${!isTS ? 'const { Schema } = mongoose;' : ''}
|
|
973
|
+
${tsInterface}
|
|
974
|
+
${schemaBody}
|
|
975
|
+
|
|
976
|
+
export const User = mongoose.model${isTS ? '<IUser>' : ''}('User', userSchema);
|
|
977
|
+
`;
|
|
978
|
+
} else {
|
|
979
|
+
// CommonJS
|
|
980
|
+
return `const mongoose = require('mongoose');
|
|
981
|
+
const bcrypt = require('bcryptjs');
|
|
982
|
+
const { Schema } = mongoose;
|
|
983
|
+
|
|
984
|
+
${schemaBody}
|
|
985
|
+
|
|
986
|
+
const User = mongoose.model('User', userSchema);
|
|
987
|
+
|
|
988
|
+
module.exports = { User };
|
|
989
|
+
`;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
function generateConfigFile(options: InitOptions): string {
|
|
994
|
+
const isTS = options.language === 'typescript';
|
|
995
|
+
const isESM = options.moduleSystem === 'esm';
|
|
996
|
+
|
|
997
|
+
const configBody = `{
|
|
998
|
+
env: process.env.NODE_ENV || 'development',
|
|
999
|
+
port: parseInt(process.env.PORT || '3000', 10),
|
|
1000
|
+
host: process.env.HOST || '0.0.0.0',
|
|
1001
|
+
|
|
1002
|
+
jwt: {
|
|
1003
|
+
secret: process.env.JWT_SECRET || 'your-secret-key',
|
|
1004
|
+
accessExpiresIn: process.env.JWT_ACCESS_EXPIRES_IN || '15m',
|
|
1005
|
+
refreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN || '7d',
|
|
1006
|
+
},
|
|
807
1007
|
|
|
808
|
-
|
|
1008
|
+
cors: {
|
|
1009
|
+
origin: process.env.CORS_ORIGIN || 'http://localhost:3000',
|
|
1010
|
+
},
|
|
1011
|
+
|
|
1012
|
+
rateLimit: {
|
|
1013
|
+
max: parseInt(process.env.RATE_LIMIT_MAX || '100', 10),
|
|
1014
|
+
},
|
|
1015
|
+
|
|
1016
|
+
log: {
|
|
1017
|
+
level: process.env.LOG_LEVEL || 'info',
|
|
1018
|
+
},
|
|
1019
|
+
}${isTS ? ' as const' : ''}`;
|
|
1020
|
+
|
|
1021
|
+
if (isESM || isTS) {
|
|
1022
|
+
return `import 'dotenv/config';
|
|
1023
|
+
|
|
1024
|
+
export const config = ${configBody};
|
|
1025
|
+
`;
|
|
1026
|
+
} else {
|
|
1027
|
+
// CommonJS
|
|
1028
|
+
return `require('dotenv').config();
|
|
1029
|
+
|
|
1030
|
+
const config = ${configBody};
|
|
1031
|
+
|
|
1032
|
+
module.exports = { config };
|
|
1033
|
+
`;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
function generateMiddlewareFile(options: InitOptions): string {
|
|
1038
|
+
const isTS = options.language === 'typescript';
|
|
1039
|
+
const isESM = options.moduleSystem === 'esm';
|
|
1040
|
+
const fileExt = isTS ? 'js' : isESM ? 'js' : 'cjs';
|
|
1041
|
+
|
|
1042
|
+
const middlewareBody = `/**
|
|
1043
|
+
* Error handler middleware
|
|
1044
|
+
*/
|
|
1045
|
+
function errorHandler(error${isTS ? ': Error' : ''}, request${isTS ? ': FastifyRequest' : ''}, reply${isTS ? ': FastifyReply' : ''})${isTS ? ': void' : ''} {
|
|
1046
|
+
logger.error({ err: error, url: request.url, method: request.method }, 'Request error');
|
|
1047
|
+
|
|
1048
|
+
const statusCode = (error${isTS ? ' as any' : ''}).statusCode || 500;
|
|
1049
|
+
const message = statusCode === 500 ? 'Internal Server Error' : error.message;
|
|
1050
|
+
|
|
1051
|
+
reply.status(statusCode).send({
|
|
1052
|
+
success: false,
|
|
1053
|
+
error: message,
|
|
1054
|
+
...(process.env.NODE_ENV === 'development' && { stack: error.stack }),
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
/**
|
|
1059
|
+
* Request logging middleware
|
|
1060
|
+
*/
|
|
1061
|
+
function requestLogger(request${isTS ? ': FastifyRequest' : ''}, reply${isTS ? ': FastifyReply' : ''}, done${isTS ? ': () => void' : ''})${isTS ? ': void' : ''} {
|
|
1062
|
+
logger.info({ url: request.url, method: request.method, ip: request.ip }, 'Incoming request');
|
|
1063
|
+
done();
|
|
1064
|
+
}`;
|
|
1065
|
+
|
|
1066
|
+
if (isESM || isTS) {
|
|
1067
|
+
return `${isTS ? "import type { FastifyRequest, FastifyReply } from 'fastify';" : ''}
|
|
1068
|
+
import { logger } from '../core/logger.${fileExt}';
|
|
1069
|
+
|
|
1070
|
+
${middlewareBody}
|
|
1071
|
+
|
|
1072
|
+
export { errorHandler, requestLogger };
|
|
1073
|
+
`;
|
|
1074
|
+
} else {
|
|
1075
|
+
// CommonJS
|
|
1076
|
+
return `const { logger } = require('../core/logger.cjs');
|
|
1077
|
+
|
|
1078
|
+
${middlewareBody}
|
|
1079
|
+
|
|
1080
|
+
module.exports = { errorHandler, requestLogger };
|
|
1081
|
+
`;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
function generateUtilsFile(options: InitOptions): string {
|
|
1086
|
+
const isTS = options.language === 'typescript';
|
|
1087
|
+
const isESM = options.moduleSystem === 'esm';
|
|
1088
|
+
|
|
1089
|
+
const utilsBody = `/**
|
|
1090
|
+
* Standard API response helper
|
|
1091
|
+
*/
|
|
1092
|
+
function apiResponse${isTS ? '<T>' : ''}(data${isTS ? ': T' : ''}, message = "Success")${isTS ? ': { success: boolean; message: string; data: T }' : ''} {
|
|
1093
|
+
return {
|
|
1094
|
+
success: true,
|
|
1095
|
+
message,
|
|
1096
|
+
data,
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/**
|
|
1101
|
+
* Error response helper
|
|
1102
|
+
*/
|
|
1103
|
+
function errorResponse(message${isTS ? ': string' : ''}, code${isTS ? '?: string' : ''})${isTS ? ': { success: boolean; error: string; code?: string }' : ''} {
|
|
1104
|
+
return {
|
|
1105
|
+
success: false,
|
|
1106
|
+
error: message,
|
|
1107
|
+
...(code && { code }),
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* Pagination helper
|
|
1113
|
+
*/
|
|
1114
|
+
function paginate${isTS ? '<T>' : ''}(data${isTS ? ': T[]' : ''}, page${isTS ? ': number' : ''}, limit${isTS ? ': number' : ''}, total${isTS ? ': number' : ''})${isTS ? ': PaginationResult<T>' : ''} {
|
|
1115
|
+
const totalPages = Math.ceil(total / limit);
|
|
1116
|
+
|
|
1117
|
+
return {
|
|
1118
|
+
data,
|
|
1119
|
+
pagination: {
|
|
1120
|
+
page,
|
|
1121
|
+
limit,
|
|
1122
|
+
total,
|
|
1123
|
+
totalPages,
|
|
1124
|
+
hasNextPage: page < totalPages,
|
|
1125
|
+
hasPrevPage: page > 1,
|
|
1126
|
+
},
|
|
1127
|
+
};
|
|
1128
|
+
}`;
|
|
1129
|
+
|
|
1130
|
+
const tsInterface = isTS
|
|
1131
|
+
? `
|
|
1132
|
+
/**
|
|
1133
|
+
* Pagination result type
|
|
1134
|
+
*/
|
|
1135
|
+
export interface PaginationResult<T> {
|
|
1136
|
+
data: T[];
|
|
1137
|
+
pagination: {
|
|
1138
|
+
page: number;
|
|
1139
|
+
limit: number;
|
|
1140
|
+
total: number;
|
|
1141
|
+
totalPages: number;
|
|
1142
|
+
hasNextPage: boolean;
|
|
1143
|
+
hasPrevPage: boolean;
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
`
|
|
1147
|
+
: '';
|
|
1148
|
+
|
|
1149
|
+
if (isESM || isTS) {
|
|
1150
|
+
return `${tsInterface}
|
|
1151
|
+
${utilsBody}
|
|
1152
|
+
|
|
1153
|
+
export { apiResponse, errorResponse, paginate };
|
|
1154
|
+
`;
|
|
1155
|
+
} else {
|
|
1156
|
+
// CommonJS
|
|
1157
|
+
return `${utilsBody}
|
|
1158
|
+
|
|
1159
|
+
module.exports = { apiResponse, errorResponse, paginate };
|
|
1160
|
+
`;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
function generateTypesFile(options: InitOptions): string {
|
|
1165
|
+
if (options.language !== 'typescript') {
|
|
1166
|
+
return '// Types file - not needed for JavaScript\n';
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
return `/**
|
|
1170
|
+
* Common type definitions
|
|
1171
|
+
*/
|
|
1172
|
+
|
|
1173
|
+
export interface ApiResponse<T = unknown> {
|
|
1174
|
+
success: boolean;
|
|
1175
|
+
message?: string;
|
|
1176
|
+
data?: T;
|
|
1177
|
+
error?: string;
|
|
1178
|
+
code?: string;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
export interface PaginatedResponse<T> {
|
|
1182
|
+
data: T[];
|
|
1183
|
+
pagination: {
|
|
1184
|
+
page: number;
|
|
1185
|
+
limit: number;
|
|
1186
|
+
total: number;
|
|
1187
|
+
totalPages: number;
|
|
1188
|
+
hasNextPage: boolean;
|
|
1189
|
+
hasPrevPage: boolean;
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
export interface RequestUser {
|
|
1194
|
+
id: string;
|
|
1195
|
+
email: string;
|
|
1196
|
+
role: string;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
declare module 'fastify' {
|
|
1200
|
+
interface FastifyRequest {
|
|
1201
|
+
user?: RequestUser;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
809
1204
|
`;
|
|
810
1205
|
}
|