From 38fa88d5bb58f17e2ec19a48630a8a73a200693c Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 13:54:41 +0530 Subject: [PATCH 01/24] chore: add vitest and test scripts --- package.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 1f45667..aa3dd51 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,9 @@ "type": "module", "main": "./bin/servergen.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage" }, "repository": { "type": "git", @@ -32,5 +34,8 @@ "chalk": "^5.4.1", "commander": "^14.0.2", "fs-extra": "^11.3.3" + }, + "devDependencies": { + "vitest": "^4.0.18" } } From 94b864d4270e8a607e78107d505cff4e8fbdc208 Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 13:54:48 +0530 Subject: [PATCH 02/24] feat: add --skip-install CLI option --- bin/servergen.js | 5 ++++- lib/app_generator.js | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/bin/servergen.js b/bin/servergen.js index 39cb81b..7a78433 100644 --- a/bin/servergen.js +++ b/bin/servergen.js @@ -32,6 +32,7 @@ program .option('-v --view ', 'Name of View Engine: Pug | Jade | EJS | HBS') .option('--db', 'Install Mongoose & the Folder Directory for it') .option('-p, --port ', 'Set default port for the app', '3000') + .option('--skip-install', 'Skip npm install step') .option('--debug', 'Enable debug logging') .parse(process.argv); @@ -48,8 +49,9 @@ handleValidationErrors(validationResult); const appName = fileName.cleanAppName(options.name); const port = parseInt(options.port, 10) || 3000; +const skipInstall = options.skipInstall || false; -logger.debug('Parsed configuration', { appName, port, framework: options.framework }); +logger.debug('Parsed configuration', { appName, port, framework: options.framework, skipInstall }); /** * Main function to run the application generator. @@ -62,6 +64,7 @@ const main = async () => { view: options.view, db: options.db, port, + skipInstall, config, }, { diff --git a/lib/app_generator.js b/lib/app_generator.js index db2e604..eaf08b2 100644 --- a/lib/app_generator.js +++ b/lib/app_generator.js @@ -24,6 +24,7 @@ class AppGenerator { * @param {string|null} options.view - The view engine name. * @param {boolean} options.db - Whether to include database configuration. * @param {number} options.port - The port number for the app. + * @param {boolean} options.skipInstall - Whether to skip npm install. * @param {Object} options.config - Configuration object from lib/config. * @param {Object} dependencies - Injected dependencies. * @param {Object} dependencies.fsHelper - File system helper module. @@ -37,6 +38,7 @@ class AppGenerator { this.view = options.view; this.db = options.db; this.port = options.port || 3000; + this.skipInstall = options.skipInstall || false; this.config = options.config; this.fsHelper = dependencies.fsHelper; @@ -59,6 +61,7 @@ class AppGenerator { appName: this.appName, framework: this.framework, port: this.port, + skipInstall: this.skipInstall, }); this.createAppFolder(); @@ -67,7 +70,10 @@ class AppGenerator { this.setupViews(); this.setupDatabase(); this.addSupportFiles(); - await this.installDependencies(); + + if (!this.skipInstall) { + await this.installDependencies(); + } this.logger?.success(`Application ${this.appName} created successfully`); } From f56d95b7e821f856dfc68c422fac00724c279c5b Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 13:54:53 +0530 Subject: [PATCH 03/24] test: add fileName unit tests --- tests/fileName.test.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/fileName.test.js diff --git a/tests/fileName.test.js b/tests/fileName.test.js new file mode 100644 index 0000000..dc56905 --- /dev/null +++ b/tests/fileName.test.js @@ -0,0 +1,32 @@ +import { describe, it, expect } from 'vitest'; +import { cleanAppName } from '../lib/fileName.js'; + +describe('cleanAppName', () => { + it('converts to lowercase', () => { + expect(cleanAppName('MyApp')).toBe('myapp'); + }); + + it('removes spaces', () => { + expect(cleanAppName('my app')).toBe('myapp'); + }); + + it('removes special characters', () => { + expect(cleanAppName('my-app_test!@#')).toBe('myapptest'); + }); + + it('removes hyphens and underscores', () => { + expect(cleanAppName('my-app_name')).toBe('myappname'); + }); + + it('handles numbers', () => { + expect(cleanAppName('app123')).toBe('app123'); + }); + + it('handles empty string', () => { + expect(cleanAppName('')).toBe(''); + }); + + it('handles only special characters', () => { + expect(cleanAppName('---___!!!')).toBe(''); + }); +}); From f82155eff1bebdb77bb363c46f863f9fbd77a589 Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 13:54:58 +0530 Subject: [PATCH 04/24] test: add validator unit tests --- tests/validator.test.js | 76 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 tests/validator.test.js diff --git a/tests/validator.test.js b/tests/validator.test.js new file mode 100644 index 0000000..a297da9 --- /dev/null +++ b/tests/validator.test.js @@ -0,0 +1,76 @@ +import { describe, it, expect } from 'vitest'; +import { validateOptions } from '../lib/validator.js'; + +const validationRules = { + frameworks: ['node', 'express'], + views: ['ejs', 'jade', 'pug', 'hbs'], +}; + +describe('validateOptions', () => { + describe('framework validation', () => { + it('accepts valid framework: express', () => { + const result = validateOptions({ framework: 'express' }, validationRules); + expect(result.isValid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + it('accepts valid framework: node', () => { + const result = validateOptions({ framework: 'node' }, validationRules); + expect(result.isValid).toBe(true); + }); + + it('rejects invalid framework', () => { + const result = validateOptions({ framework: 'invalid' }, validationRules); + expect(result.isValid).toBe(false); + expect(result.errors).toHaveLength(1); + expect(result.errors[0]).toContain('Invalid framework'); + }); + + it('passes when no framework specified', () => { + const result = validateOptions({}, validationRules); + expect(result.isValid).toBe(true); + }); + }); + + describe('view validation', () => { + it('accepts valid view: ejs', () => { + const result = validateOptions({ view: 'ejs' }, validationRules); + expect(result.isValid).toBe(true); + }); + + it('accepts valid view: pug', () => { + const result = validateOptions({ view: 'pug' }, validationRules); + expect(result.isValid).toBe(true); + }); + + it('rejects invalid view', () => { + const result = validateOptions({ view: 'invalid' }, validationRules); + expect(result.isValid).toBe(false); + expect(result.errors[0]).toContain('Invalid view engine'); + }); + + it('passes when no view specified', () => { + const result = validateOptions({}, validationRules); + expect(result.isValid).toBe(true); + }); + }); + + describe('combined validation', () => { + it('validates both framework and view', () => { + const result = validateOptions( + { framework: 'express', view: 'ejs' }, + validationRules + ); + expect(result.isValid).toBe(true); + }); + + it('returns multiple errors for multiple invalid options', () => { + const result = validateOptions( + { framework: 'bad', view: 'worse' }, + validationRules + ); + expect(result.isValid).toBe(false); + expect(result.errors).toHaveLength(2); + }); + }); +}); From 46a61bdfa7047b3345adf185f6815d405d7bd0cf Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 13:55:03 +0530 Subject: [PATCH 05/24] test: add config unit tests --- tests/config.test.js | 72 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 tests/config.test.js diff --git a/tests/config.test.js b/tests/config.test.js new file mode 100644 index 0000000..9ca76d5 --- /dev/null +++ b/tests/config.test.js @@ -0,0 +1,72 @@ +import { describe, it, expect } from 'vitest'; +import { getConfig } from '../lib/config.js'; +import path from 'path'; + +describe('getConfig', () => { + const baseDir = '/test/base'; + const cwd = '/test/cwd'; + + it('returns configuration object', () => { + const config = getConfig(baseDir, cwd); + expect(config).toBeDefined(); + expect(config.paths).toBeDefined(); + expect(config.validation).toBeDefined(); + expect(config.defaults).toBeDefined(); + }); + + describe('paths', () => { + it('sets express template path', () => { + const config = getConfig(baseDir, cwd); + expect(config.paths.templates.express).toBe( + path.join(baseDir, '..', 'templates', 'express') + ); + }); + + it('sets node template path', () => { + const config = getConfig(baseDir, cwd); + expect(config.paths.templates.node).toBe( + path.join(baseDir, '..', 'templates', 'node') + ); + }); + + it('sets views path', () => { + const config = getConfig(baseDir, cwd); + expect(config.paths.templates.views).toBe( + path.join(baseDir, '..', 'templates', 'express', 'views') + ); + }); + + it('sets cwd correctly', () => { + const config = getConfig(baseDir, cwd); + expect(config.paths.cwd).toBe(cwd); + }); + }); + + describe('validation rules', () => { + it('includes valid frameworks', () => { + const config = getConfig(baseDir, cwd); + expect(config.validation.frameworks).toContain('node'); + expect(config.validation.frameworks).toContain('express'); + }); + + it('includes valid views', () => { + const config = getConfig(baseDir, cwd); + expect(config.validation.views).toContain('ejs'); + expect(config.validation.views).toContain('pug'); + expect(config.validation.views).toContain('jade'); + expect(config.validation.views).toContain('hbs'); + }); + }); + + describe('defaults', () => { + it('sets default framework to express', () => { + const config = getConfig(baseDir, cwd); + expect(config.defaults.framework).toBe('express'); + }); + + it('sets default port to 3000', () => { + const config = getConfig(baseDir, cwd); + expect(config.defaults.port).toBe(3000); + }); + }); +}); From e66da31db2eb251d0c45da66a09fbaa3aab327cd Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 13:55:10 +0530 Subject: [PATCH 06/24] test: add constants unit tests --- tests/constants.test.js | 76 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 tests/constants.test.js diff --git a/tests/constants.test.js b/tests/constants.test.js new file mode 100644 index 0000000..ec5e16e --- /dev/null +++ b/tests/constants.test.js @@ -0,0 +1,76 @@ +import { describe, it, expect } from 'vitest'; +import { + LOG_LEVELS, + VIEW_ENGINES, + VALID_VIEWS, + DEPENDENCY_VERSIONS, +} from '../lib/constants.js'; + +describe('constants', () => { + describe('LOG_LEVELS', () => { + it('defines ERROR as 0', () => { + expect(LOG_LEVELS.ERROR).toBe(0); + }); + + it('defines WARN as 1', () => { + expect(LOG_LEVELS.WARN).toBe(1); + }); + + it('defines INFO as 2', () => { + expect(LOG_LEVELS.INFO).toBe(2); + }); + + it('defines DEBUG as 3', () => { + expect(LOG_LEVELS.DEBUG).toBe(3); + }); + }); + + describe('VIEW_ENGINES', () => { + it('includes ejs', () => { + expect(VIEW_ENGINES.ejs).toBeDefined(); + }); + + it('includes pug', () => { + expect(VIEW_ENGINES.pug).toBeDefined(); + }); + + it('includes jade', () => { + expect(VIEW_ENGINES.jade).toBeDefined(); + }); + + it('includes hbs', () => { + expect(VIEW_ENGINES.hbs).toBeDefined(); + }); + }); + + describe('VALID_VIEWS', () => { + it('is array of view engine names', () => { + expect(VALID_VIEWS).toContain('ejs'); + expect(VALID_VIEWS).toContain('pug'); + expect(VALID_VIEWS).toContain('jade'); + expect(VALID_VIEWS).toContain('hbs'); + }); + + it('matches VIEW_ENGINES keys', () => { + expect(VALID_VIEWS).toEqual(Object.keys(VIEW_ENGINES)); + }); + }); + + describe('DEPENDENCY_VERSIONS', () => { + it('includes nodemon', () => { + expect(DEPENDENCY_VERSIONS.nodemon).toBeDefined(); + }); + + it('includes cors', () => { + expect(DEPENDENCY_VERSIONS.cors).toBeDefined(); + }); + + it('includes express', () => { + expect(DEPENDENCY_VERSIONS.express).toBeDefined(); + }); + + it('includes mongoose', () => { + expect(DEPENDENCY_VERSIONS.mongoose).toBeDefined(); + }); + }); +}); From 10854dd48c5432375bad10f27fd8d77d11ef196c Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 13:55:17 +0530 Subject: [PATCH 07/24] test: add build_helper unit tests --- tests/build_helper.test.js | 68 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 tests/build_helper.test.js diff --git a/tests/build_helper.test.js b/tests/build_helper.test.js new file mode 100644 index 0000000..33b9dc4 --- /dev/null +++ b/tests/build_helper.test.js @@ -0,0 +1,68 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import fs from 'fs-extra'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as buildHelper from '../lib/build_helper.js'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const testDir = path.join(__dirname, '.test-output'); + +describe('build_helper', () => { + beforeEach(() => { + fs.ensureDirSync(testDir); + }); + + afterEach(() => { + fs.removeSync(testDir); + }); + + describe('createDir', () => { + it('creates a directory inside app directory', () => { + buildHelper.createDir(testDir, 'controllers'); + const dirPath = path.join(testDir, 'controllers'); + expect(fs.existsSync(dirPath)).toBe(true); + }); + + it('creates nested directory structure', () => { + buildHelper.createDir(testDir, 'config'); + buildHelper.createDir(testDir, 'routes'); + expect(fs.existsSync(path.join(testDir, 'config'))).toBe(true); + expect(fs.existsSync(path.join(testDir, 'routes'))).toBe(true); + }); + }); + + describe('buildFilewithContents', () => { + it('copies file content to destination', () => { + const sourceFile = path.join(testDir, 'source.txt'); + fs.writeFileSync(sourceFile, 'test content'); + + buildHelper.buildFilewithContents(sourceFile, testDir, 'dest.txt'); + + const destContent = fs.readFileSync(path.join(testDir, 'dest.txt'), 'utf-8'); + expect(destContent).toBe('test content'); + }); + }); + + describe('buildFolderforApp', () => { + it('creates a new folder', () => { + const newFolder = path.join(testDir, 'new-app'); + buildHelper.buildFolderforApp(newFolder); + expect(fs.existsSync(newFolder)).toBe(true); + }); + + it('exits if folder already exists', () => { + const existingFolder = path.join(testDir, 'existing'); + fs.mkdirSync(existingFolder); + + const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => { + throw new Error('process.exit called'); + }); + + expect(() => { + buildHelper.buildFolderforApp(existingFolder); + }).toThrow('process.exit called'); + + mockExit.mockRestore(); + }); + }); +}); From d71ff073cc2c4631856bb29c9b794e02cddcf742 Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 13:55:22 +0530 Subject: [PATCH 08/24] test: add CLI integration tests --- tests/integration.test.js | 136 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 tests/integration.test.js diff --git a/tests/integration.test.js b/tests/integration.test.js new file mode 100644 index 0000000..e1ead2f --- /dev/null +++ b/tests/integration.test.js @@ -0,0 +1,136 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import fs from 'fs-extra'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { execSync } from 'child_process'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const projectRoot = path.join(__dirname, '..'); +const testOutput = path.join(__dirname, '.integration-output'); + +describe('CLI Integration', () => { + beforeEach(() => { + fs.ensureDirSync(testOutput); + }); + + afterEach(() => { + fs.removeSync(testOutput); + }); + + const runCLI = (args) => { + const cmd = `node ${path.join(projectRoot, 'bin', 'servergen.js')} ${args}`; + return execSync(cmd, { + cwd: testOutput, + encoding: 'utf-8', + timeout: 60000, + }); + }; + + describe('help command', () => { + it('displays help information', () => { + const output = runCLI('--help'); + expect(output).toContain('Usage:'); + expect(output).toContain('-n, --name'); + expect(output).toContain('-f, --framework'); + }); + + it('displays version', () => { + const output = runCLI('--version'); + expect(output).toMatch(/\d+\.\d+\.\d+/); + }); + }); + + describe('Express app generation', () => { + it('generates Express app with correct structure', () => { + runCLI('-n testapp -f express --skip-install'); + + const appDir = path.join(testOutput, 'testapp'); + expect(fs.existsSync(appDir)).toBe(true); + expect(fs.existsSync(path.join(appDir, 'index.js'))).toBe(true); + expect(fs.existsSync(path.join(appDir, 'package.json'))).toBe(true); + expect(fs.existsSync(path.join(appDir, 'routes'))).toBe(true); + expect(fs.existsSync(path.join(appDir, 'controllers'))).toBe(true); + expect(fs.existsSync(path.join(appDir, 'model'))).toBe(true); + }); + + it('generates package.json with express dependency', () => { + runCLI('-n expresstest -f express --skip-install'); + + const pkgPath = path.join(testOutput, 'expresstest', 'package.json'); + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); + + expect(pkg.name).toBe('expresstest'); + expect(pkg.dependencies.express).toBeDefined(); + expect(pkg.dependencies.nodemon).toBeDefined(); + expect(pkg.dependencies.cors).toBeDefined(); + }); + + it('includes view engine when specified', () => { + runCLI('-n viewtest -f express -v ejs --skip-install'); + + const pkgPath = path.join(testOutput, 'viewtest', 'package.json'); + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); + + expect(pkg.dependencies.ejs).toBeDefined(); + }); + + it('includes mongoose when --db flag used', () => { + runCLI('-n dbtest -f express --db --skip-install'); + + const pkgPath = path.join(testOutput, 'dbtest', 'package.json'); + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); + + expect(pkg.dependencies.mongoose).toBeDefined(); + expect(fs.existsSync(path.join(testOutput, 'dbtest', 'config'))).toBe(true); + }); + }); + + describe('Node app generation', () => { + it('generates Node app with correct structure', () => { + runCLI('-n nodetest -f node --skip-install'); + + const appDir = path.join(testOutput, 'nodetest'); + expect(fs.existsSync(appDir)).toBe(true); + expect(fs.existsSync(path.join(appDir, 'index.js'))).toBe(true); + expect(fs.existsSync(path.join(appDir, 'package.json'))).toBe(true); + }); + + it('generates package.json without express dependency', () => { + runCLI('-n purenode -f node --skip-install'); + + const pkgPath = path.join(testOutput, 'purenode', 'package.json'); + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); + + expect(pkg.dependencies.express).toBeUndefined(); + expect(pkg.dependencies.nodemon).toBeDefined(); + }); + }); + + describe('support files', () => { + it('includes .gitignore', () => { + runCLI('-n gittest -f express --skip-install'); + expect(fs.existsSync(path.join(testOutput, 'gittest', '.gitignore'))).toBe(true); + }); + + it('includes Dockerfile', () => { + runCLI('-n dockertest -f express --skip-install'); + expect(fs.existsSync(path.join(testOutput, 'dockertest', 'Dockerfile'))).toBe(true); + }); + + it('includes .dockerignore', () => { + runCLI('-n dockertest2 -f express --skip-install'); + expect(fs.existsSync(path.join(testOutput, 'dockertest2', '.dockerignore'))).toBe(true); + }); + }); + + describe('custom port', () => { + it('configures custom port in index.js', () => { + runCLI('-n porttest -f express -p 8080 --skip-install'); + + const indexPath = path.join(testOutput, 'porttest', 'index.js'); + const content = fs.readFileSync(indexPath, 'utf-8'); + + expect(content).toContain('8080'); + }); + }); +}); From 26fca503453aa6839698750c84eb0d77aab89d99 Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 13:55:29 +0530 Subject: [PATCH 09/24] ci: add GitHub Actions workflow for tests --- .github/workflows/ci.yml | 48 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1a936a8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [18, 20, 22] + + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test + + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Check syntax + run: node --check bin/servergen.js From 1f32c46e24bef4e1bd37890ee9a5342aa5afbe70 Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 14:14:17 +0530 Subject: [PATCH 10/24] fix: use npm install instead of npm ci, remove lint job --- .github/workflows/ci.yml | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a936a8..5fa595f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,28 +21,9 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - cache: 'npm' - name: Install dependencies - run: npm ci + run: npm install - name: Run tests run: npm test - - lint: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Use Node.js 20 - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Check syntax - run: node --check bin/servergen.js From 7989909dfeb1e9a65e36d0d22bdba9da0a34f377 Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 14:14:23 +0530 Subject: [PATCH 11/24] feat: add vitest config with coverage thresholds --- vitest.config.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 vitest.config.js diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 0000000..b0b9d4b --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,18 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['tests/unit/**/*.test.js', 'tests/integration/**/*.test.js'], + coverage: { + provider: 'v8', + reporter: ['text', 'html'], + exclude: ['tests/**', 'node_modules/**'], + thresholds: { + statements: 70, + branches: 60, + functions: 70, + lines: 70, + }, + }, + }, +}); From 0f3abdae70f0cb320eb882885e42463fa614b06b Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 14:14:28 +0530 Subject: [PATCH 12/24] refactor: split tests into unit and integration directories --- tests/{ => integration}/integration.test.js | 2 +- tests/{ => unit}/build_helper.test.js | 2 +- tests/{ => unit}/config.test.js | 2 +- tests/{ => unit}/constants.test.js | 2 +- tests/{ => unit}/fileName.test.js | 2 +- tests/{ => unit}/validator.test.js | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename tests/{ => integration}/integration.test.js (98%) rename tests/{ => unit}/build_helper.test.js (97%) rename tests/{ => unit}/config.test.js (97%) rename tests/{ => unit}/constants.test.js (98%) rename tests/{ => unit}/fileName.test.js (93%) rename tests/{ => unit}/validator.test.js (97%) diff --git a/tests/integration.test.js b/tests/integration/integration.test.js similarity index 98% rename from tests/integration.test.js rename to tests/integration/integration.test.js index e1ead2f..25942f0 100644 --- a/tests/integration.test.js +++ b/tests/integration/integration.test.js @@ -5,7 +5,7 @@ import { fileURLToPath } from 'url'; import { execSync } from 'child_process'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const projectRoot = path.join(__dirname, '..'); +const projectRoot = path.join(__dirname, '..', '..'); const testOutput = path.join(__dirname, '.integration-output'); describe('CLI Integration', () => { diff --git a/tests/build_helper.test.js b/tests/unit/build_helper.test.js similarity index 97% rename from tests/build_helper.test.js rename to tests/unit/build_helper.test.js index 33b9dc4..9d7cb97 100644 --- a/tests/build_helper.test.js +++ b/tests/unit/build_helper.test.js @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import fs from 'fs-extra'; import path from 'path'; import { fileURLToPath } from 'url'; -import * as buildHelper from '../lib/build_helper.js'; +import * as buildHelper from '../../lib/build_helper.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const testDir = path.join(__dirname, '.test-output'); diff --git a/tests/config.test.js b/tests/unit/config.test.js similarity index 97% rename from tests/config.test.js rename to tests/unit/config.test.js index 9ca76d5..8dac9b5 100644 --- a/tests/config.test.js +++ b/tests/unit/config.test.js @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { getConfig } from '../lib/config.js'; +import { getConfig } from '../../lib/config.js'; import path from 'path'; describe('getConfig', () => { diff --git a/tests/constants.test.js b/tests/unit/constants.test.js similarity index 98% rename from tests/constants.test.js rename to tests/unit/constants.test.js index ec5e16e..1d17006 100644 --- a/tests/constants.test.js +++ b/tests/unit/constants.test.js @@ -4,7 +4,7 @@ import { VIEW_ENGINES, VALID_VIEWS, DEPENDENCY_VERSIONS, -} from '../lib/constants.js'; +} from '../../lib/constants.js'; describe('constants', () => { describe('LOG_LEVELS', () => { diff --git a/tests/fileName.test.js b/tests/unit/fileName.test.js similarity index 93% rename from tests/fileName.test.js rename to tests/unit/fileName.test.js index dc56905..6660f77 100644 --- a/tests/fileName.test.js +++ b/tests/unit/fileName.test.js @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { cleanAppName } from '../lib/fileName.js'; +import { cleanAppName } from '../../lib/fileName.js'; describe('cleanAppName', () => { it('converts to lowercase', () => { diff --git a/tests/validator.test.js b/tests/unit/validator.test.js similarity index 97% rename from tests/validator.test.js rename to tests/unit/validator.test.js index a297da9..73ea7ac 100644 --- a/tests/validator.test.js +++ b/tests/unit/validator.test.js @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { validateOptions } from '../lib/validator.js'; +import { validateOptions } from '../../lib/validator.js'; const validationRules = { frameworks: ['node', 'express'], From c357d0e283926a656d51505b8395c29d3aa475e3 Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 17:10:47 +0530 Subject: [PATCH 13/24] feat: add fastify dependency versions --- lib/constants.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/constants.js b/lib/constants.js index 6de01dd..569d4d8 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -41,4 +41,9 @@ export const DEPENDENCY_VERSIONS = { cors: '^2.8.5', express: '^4.21.0', mongoose: '^8.5.0', + fastify: '^5.2.1', + '@fastify/cors': '^11.0.0', + '@fastify/formbody': '^8.0.2', + '@fastify/helmet': '^13.0.1', + '@fastify/view': '^10.0.1', }; From b9a6acf4460249e61e3f276c5cefbab1d4d5f75f Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 17:10:53 +0530 Subject: [PATCH 14/24] feat: add fastify to framework configuration --- lib/config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/config.js b/lib/config.js index 1efbe1b..28ecb3d 100644 --- a/lib/config.js +++ b/lib/config.js @@ -17,12 +17,13 @@ export const getConfig = (baseDir, cwd) => { templates: { express: path.join(baseDir, '..', 'templates', 'express'), node: path.join(baseDir, '..', 'templates', 'node'), + fastify: path.join(baseDir, '..', 'templates', 'fastify'), views: path.join(baseDir, '..', 'templates', 'express', 'views'), }, cwd, }, validation: { - frameworks: ['node', 'express'], + frameworks: ['node', 'express', 'fastify'], views: ['ejs', 'jade', 'pug', 'hbs'], }, defaults: { From 1433e7460e35d22653d606381982eadc936252c4 Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 17:10:57 +0530 Subject: [PATCH 15/24] feat: add fastify template files --- templates/fastify/.dockerignore | 9 ++++ templates/fastify/.env.example | 7 +++ templates/fastify/.gitignore | 7 +++ templates/fastify/Dockerfile | 21 ++++++++ templates/fastify/README.md | 64 +++++++++++++++++++++++ templates/fastify/config/mongoose.js | 37 ++++++++++++++ templates/fastify/index(config).js | 76 ++++++++++++++++++++++++++++ templates/fastify/index.js | 73 ++++++++++++++++++++++++++ templates/fastify/routes/index.js | 61 ++++++++++++++++++++++ 9 files changed, 355 insertions(+) create mode 100644 templates/fastify/.dockerignore create mode 100644 templates/fastify/.env.example create mode 100644 templates/fastify/.gitignore create mode 100644 templates/fastify/Dockerfile create mode 100644 templates/fastify/README.md create mode 100644 templates/fastify/config/mongoose.js create mode 100644 templates/fastify/index(config).js create mode 100644 templates/fastify/index.js create mode 100644 templates/fastify/routes/index.js diff --git a/templates/fastify/.dockerignore b/templates/fastify/.dockerignore new file mode 100644 index 0000000..b8512b1 --- /dev/null +++ b/templates/fastify/.dockerignore @@ -0,0 +1,9 @@ +node_modules +npm-debug.log +.git +.gitignore +.env +*.md +.DS_Store +coverage +.nyc_output diff --git a/templates/fastify/.env.example b/templates/fastify/.env.example new file mode 100644 index 0000000..a02675d --- /dev/null +++ b/templates/fastify/.env.example @@ -0,0 +1,7 @@ +# Server Configuration +PORT=3000 +HOST=0.0.0.0 +NODE_ENV=development + +# Database Configuration (if using --db flag) +MONGODB_URI=mongodb://localhost:27017/servergen diff --git a/templates/fastify/.gitignore b/templates/fastify/.gitignore new file mode 100644 index 0000000..c3b459a --- /dev/null +++ b/templates/fastify/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +.env +*.log +.DS_Store +coverage/ +.nyc_output/ +dist/ diff --git a/templates/fastify/Dockerfile b/templates/fastify/Dockerfile new file mode 100644 index 0000000..27bdb0c --- /dev/null +++ b/templates/fastify/Dockerfile @@ -0,0 +1,21 @@ +FROM node:20-alpine + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm ci --only=production + +# Copy application source +COPY . . + +# Expose the application port +EXPOSE 3000 + +# Set environment to production +ENV NODE_ENV=production + +# Run the application +CMD ["node", "index.js"] diff --git a/templates/fastify/README.md b/templates/fastify/README.md new file mode 100644 index 0000000..d656327 --- /dev/null +++ b/templates/fastify/README.md @@ -0,0 +1,64 @@ +# Fastify Application + +A high-performance web server built with [Fastify](https://fastify.dev/). + +## Features + +- Fast JSON parsing and serialization +- Built-in schema validation +- Structured logging with Pino +- Security headers via @fastify/helmet +- CORS support +- Graceful shutdown handling +- Health check endpoint + +## Getting Started + +### Development + +```bash +npm start +``` + +The server will start with pretty-printed logs in development mode. + +### Production + +```bash +NODE_ENV=production node index.js +``` + +In production, logs are output as JSON for log aggregation systems. + +## API Endpoints + +- `GET /` - Welcome message +- `POST /` - Echo request body +- `GET /health` - Health check for container orchestration + +## Configuration + +Environment variables: + +| Variable | Default | Description | +|----------|---------|-------------| +| PORT | 3000 | Server port | +| HOST | 0.0.0.0 | Server host | +| NODE_ENV | development | Environment (development/production/test) | +| MONGODB_URI | mongodb://localhost:27017/servergen | MongoDB connection string | + +## Production Recommendations + +1. **Use a Reverse Proxy**: Deploy behind nginx or HAProxy for TLS termination, load balancing, and static file serving. + +2. **Container Deployment**: The included Dockerfile is production-ready. Build with: + ```bash + docker build -t my-fastify-app . + docker run -p 3000:3000 my-fastify-app + ``` + +3. **Kubernetes**: The `/health` endpoint is designed for readiness/liveness probes. + +## License + +MIT diff --git a/templates/fastify/config/mongoose.js b/templates/fastify/config/mongoose.js new file mode 100644 index 0000000..85be8fe --- /dev/null +++ b/templates/fastify/config/mongoose.js @@ -0,0 +1,37 @@ +/** + * MongoDB connection plugin for Fastify. + * @description Connects to MongoDB using Mongoose and decorates the Fastify instance. + */ + +'use strict'; + +const fp = require('fastify-plugin'); +const mongoose = require('mongoose'); + +/** + * Mongoose connection plugin. + * @param {FastifyInstance} fastify - Encapsulated Fastify instance. + * @param {Object} options - Plugin options. + */ +async function mongooseConnector(fastify, options) { + const uri = process.env.MONGODB_URI || 'mongodb://localhost:27017/servergen'; + + try { + await mongoose.connect(uri); + fastify.log.info('Connected to MongoDB'); + + // Decorate fastify with mongoose instance + fastify.decorate('mongoose', mongoose); + + // Close connection on fastify close + fastify.addHook('onClose', async (instance) => { + await mongoose.connection.close(); + instance.log.info('MongoDB connection closed'); + }); + } catch (err) { + fastify.log.error('MongoDB connection error:', err); + throw err; + } +} + +module.exports = fp(mongooseConnector); diff --git a/templates/fastify/index(config).js b/templates/fastify/index(config).js new file mode 100644 index 0000000..b381246 --- /dev/null +++ b/templates/fastify/index(config).js @@ -0,0 +1,76 @@ +/** + * Fastify application entry point with MongoDB configuration. + * @description High-performance server with logging, security headers, and database. + */ + +'use strict'; + +const Fastify = require('fastify'); + +const envToLogger = { + development: { + transport: { + target: 'pino-pretty', + options: { + translateTime: 'HH:MM:ss Z', + ignore: 'pid,hostname', + }, + }, + }, + production: true, + test: false, +}; + +const environment = process.env.NODE_ENV || 'development'; + +const fastify = Fastify({ + logger: envToLogger[environment] ?? true, +}); + +// Register plugins +fastify.register(require('@fastify/cors')); +fastify.register(require('@fastify/formbody')); +fastify.register(require('@fastify/helmet')); + +// Views + +// Register database connector +fastify.register(require('./config/mongoose')); + +// Register routes +fastify.register(require('./routes')); + +/** + * Health check endpoint for container orchestration. + */ +fastify.get('/health', async (request, reply) => { + return { status: 'ok', timestamp: new Date().toISOString() }; +}); + +/** + * Graceful shutdown handler. + */ +const closeGracefully = async (signal) => { + fastify.log.info(`Received signal: ${signal}`); + await fastify.close(); + process.exit(0); +}; + +process.on('SIGINT', closeGracefully); +process.on('SIGTERM', closeGracefully); + +/** + * Start the server. + */ +const start = async () => { + try { + const port = process.env.PORT || 3000; + const host = process.env.HOST || '0.0.0.0'; + await fastify.listen({ port, host }); + } catch (err) { + fastify.log.error(err); + process.exit(1); + } +}; + +start(); diff --git a/templates/fastify/index.js b/templates/fastify/index.js new file mode 100644 index 0000000..afe5408 --- /dev/null +++ b/templates/fastify/index.js @@ -0,0 +1,73 @@ +/** + * Fastify application entry point. + * @description High-performance server with logging and security headers. + */ + +'use strict'; + +const Fastify = require('fastify'); + +const envToLogger = { + development: { + transport: { + target: 'pino-pretty', + options: { + translateTime: 'HH:MM:ss Z', + ignore: 'pid,hostname', + }, + }, + }, + production: true, + test: false, +}; + +const environment = process.env.NODE_ENV || 'development'; + +const fastify = Fastify({ + logger: envToLogger[environment] ?? true, +}); + +// Register plugins +fastify.register(require('@fastify/cors')); +fastify.register(require('@fastify/formbody')); +fastify.register(require('@fastify/helmet')); + +// Views + +// Register routes +fastify.register(require('./routes')); + +/** + * Health check endpoint for container orchestration. + */ +fastify.get('/health', async (request, reply) => { + return { status: 'ok', timestamp: new Date().toISOString() }; +}); + +/** + * Graceful shutdown handler. + */ +const closeGracefully = async (signal) => { + fastify.log.info(`Received signal: ${signal}`); + await fastify.close(); + process.exit(0); +}; + +process.on('SIGINT', closeGracefully); +process.on('SIGTERM', closeGracefully); + +/** + * Start the server. + */ +const start = async () => { + try { + const port = process.env.PORT || 3000; + const host = process.env.HOST || '0.0.0.0'; + await fastify.listen({ port, host }); + } catch (err) { + fastify.log.error(err); + process.exit(1); + } +}; + +start(); diff --git a/templates/fastify/routes/index.js b/templates/fastify/routes/index.js new file mode 100644 index 0000000..ec49fbd --- /dev/null +++ b/templates/fastify/routes/index.js @@ -0,0 +1,61 @@ +/** + * Fastify routes plugin. + * @description Defines API routes for the application. + */ + +'use strict'; + +/** + * Route plugin that registers all application routes. + * @param {FastifyInstance} fastify - Encapsulated Fastify instance. + * @param {Object} options - Plugin options. + */ +async function routes(fastify, options) { + /** + * GET / - Returns welcome message. + */ + fastify.get('/', { + schema: { + response: { + 200: { + type: 'object', + properties: { + message: { type: 'string' }, + }, + }, + }, + }, + }, async (request, reply) => { + return { message: 'Welcome to ServerGen!' }; + }); + + /** + * POST / - Returns welcome message with request body. + */ + fastify.post('/', { + schema: { + body: { + type: 'object', + properties: { + name: { type: 'string' }, + }, + }, + response: { + 200: { + type: 'object', + properties: { + message: { type: 'string' }, + data: { type: 'object' }, + }, + }, + }, + }, + }, async (request, reply) => { + return { + message: 'Welcome to ServerGen!', + data: request.body, + }; + }); +} + +module.exports = routes; From bbb982ffd4b7f91d4f006d5b6c5351070a584207 Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 17:11:01 +0530 Subject: [PATCH 16/24] feat: add fastify support to app generator --- lib/app_generator.js | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/lib/app_generator.js b/lib/app_generator.js index eaf08b2..ff97c45 100644 --- a/lib/app_generator.js +++ b/lib/app_generator.js @@ -20,7 +20,7 @@ class AppGenerator { * Creates an AppGenerator instance. * @param {Object} options - Generation options. * @param {string} options.appName - The application name. - * @param {string} options.framework - The framework type ('node' or 'express'). + * @param {string} options.framework - The framework type ('node', 'express', or 'fastify'). * @param {string|null} options.view - The view engine name. * @param {boolean} options.db - Whether to include database configuration. * @param {number} options.port - The port number for the app. @@ -47,9 +47,23 @@ class AppGenerator { this.logger = dependencies.logger; this.folderDir = path.join(this.config.paths.cwd, this.appName); - this.templatesDir = this.framework === 'node' - ? this.config.paths.templates.node - : this.config.paths.templates.express; + this.templatesDir = this._getTemplatesDir(); + } + + /** + * Returns the appropriate templates directory based on framework. + * @returns {string} Path to templates directory. + * @private + */ + _getTemplatesDir() { + switch (this.framework) { + case 'node': + return this.config.paths.templates.node; + case 'fastify': + return this.config.paths.templates.fastify; + default: + return this.config.paths.templates.express; + } } /** @@ -67,8 +81,8 @@ class AppGenerator { this.createAppFolder(); this.createAppStructure(); this.configurePort(); - this.setupViews(); this.setupDatabase(); + this.setupViews(); this.addSupportFiles(); if (!this.skipInstall) { @@ -99,6 +113,14 @@ class AppGenerator { this.view, this.db ); + } else if (this.framework === 'fastify') { + this.fileCreator.createFastifyApp( + this.templatesDir, + this.folderDir, + this.appName, + this.view, + this.db + ); } else { this.fileCreator.createExpressApp( this.templatesDir, @@ -149,7 +171,8 @@ class AppGenerator { if (this.db) { this.fileCreator.handleConfig( this.folderDir, - this.config.paths.templates.express + this.templatesDir, + this.framework ); } } From 860bd0f014f6745eb6cee5913bcaa0816b7ab1d5 Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 17:11:08 +0530 Subject: [PATCH 17/24] feat: add fastify app creation in file generator --- lib/file_generator.js | 57 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/lib/file_generator.js b/lib/file_generator.js index 8f73884..8b3a39b 100644 --- a/lib/file_generator.js +++ b/lib/file_generator.js @@ -37,7 +37,7 @@ const generateMVC = (folderDir, templatesDir) => { * @param {string} appName - The application name. * @param {string|null} view - The view engine name (ejs, pug, jade, hbs). * @param {boolean} config - Whether to include mongoose configuration. - * @param {string} framework - The framework type ('node' or 'express'). + * @param {string} framework - The framework type ('node', 'express', or 'fastify'). */ const generatePackage = (folderDir, appName, view, config, framework) => { const pkg = { @@ -50,20 +50,40 @@ const generatePackage = (folderDir, appName, view, config, framework) => { }, dependencies: { nodemon: DEPENDENCY_VERSIONS.nodemon, - cors: DEPENDENCY_VERSIONS.cors, }, + devDependencies: {}, }; if (framework === 'express') { pkg.dependencies.express = DEPENDENCY_VERSIONS.express; + pkg.dependencies.cors = DEPENDENCY_VERSIONS.cors; + } else if (framework === 'fastify') { + pkg.dependencies.fastify = DEPENDENCY_VERSIONS.fastify; + pkg.dependencies['@fastify/cors'] = DEPENDENCY_VERSIONS['@fastify/cors']; + pkg.dependencies['@fastify/formbody'] = DEPENDENCY_VERSIONS['@fastify/formbody']; + pkg.dependencies['@fastify/helmet'] = DEPENDENCY_VERSIONS['@fastify/helmet']; + pkg.devDependencies['pino-pretty'] = '^13.0.0'; + } else { + pkg.dependencies.cors = DEPENDENCY_VERSIONS.cors; } if (view && VIEW_ENGINES[view]) { pkg.dependencies[view] = VIEW_ENGINES[view]; + if (framework === 'fastify') { + pkg.dependencies['@fastify/view'] = DEPENDENCY_VERSIONS['@fastify/view']; + } } if (config) { pkg.dependencies.mongoose = DEPENDENCY_VERSIONS.mongoose; + if (framework === 'fastify') { + pkg.dependencies['fastify-plugin'] = '^5.0.1'; + } + } + + // Remove empty devDependencies + if (Object.keys(pkg.devDependencies).length === 0) { + delete pkg.devDependencies; } fs.writeFileSync( @@ -104,6 +124,22 @@ const createNodeApp = (templatesDir, folderDir, appName, view, config) => { console.log('Generating NodeJS application..'); }; +/** + * Creates a Fastify application with plugin-based structure. + * @param {string} templatesDir - Path to Fastify templates. + * @param {string} folderDir - The application directory path. + * @param {string} appName - The application name. + * @param {string|null} view - The view engine name. + * @param {boolean} config - Whether to include mongoose configuration. + */ +const createFastifyApp = (templatesDir, folderDir, appName, view, config) => { + const basePath = path.join(templatesDir, 'index.js'); + fs_helper.buildFilewithContents(basePath, folderDir, 'index.js'); + generateMVC(folderDir, templatesDir); + generatePackage(folderDir, appName, view, config, 'fastify'); + console.log('Generating Fastify application..'); +}; + /** * Handles view engine setup for the application. * @param {string} folderDir - The application directory path. @@ -140,12 +176,23 @@ const handleViews = (folderDir, viewsDir, view) => { * @param {boolean} addView - Whether to add or remove view configuration. */ const manageViews = (folderPath, viewName, addView) => { - const viewString = `app.set('view engine','${viewName}') \n app.set('views', path.join(__dirname, 'views'));`; const jsFile = path.join(folderPath, 'index.js'); try { let data = fs.readFileSync(jsFile, 'utf8'); + const isFastify = data.includes('Fastify'); + if (addView) { + let viewString; + if (isFastify) { + viewString = `const path = require('path'); +fastify.register(require('@fastify/view'), { + engine: { ${viewName}: require('${viewName}') }, + root: path.join(__dirname, 'views'), +});`; + } else { + viewString = `app.set('view engine','${viewName}') \n app.set('views', path.join(__dirname, 'views'));`; + } data = data.replace('// Views', viewString); } else { data = data.replace('// Views', ''); @@ -160,8 +207,9 @@ const manageViews = (folderPath, viewName, addView) => { * Sets up MongoDB/Mongoose configuration files. * @param {string} folderDir - The application directory path. * @param {string} templatesDir - Path to templates directory. + * @param {string} framework - The framework type ('node', 'express', or 'fastify'). */ -const handleConfig = (folderDir, templatesDir) => { +const handleConfig = (folderDir, templatesDir, framework) => { console.log('Configuring Mongoose..'); fs_helper.createDir(folderDir, 'config'); const basePath = path.join(templatesDir, 'config', 'mongoose.js'); @@ -224,6 +272,7 @@ const addEnvExample = (folderDir, templatesDir) => { export { createExpressApp, createNodeApp, + createFastifyApp, handleViews, handleConfig, addGitIgnore, From 733f54a85c2e57474682821cf5d7b958b9b2b358 Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 17:11:11 +0530 Subject: [PATCH 18/24] docs: update cli help to include fastify --- bin/servergen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/servergen.js b/bin/servergen.js index 7a78433..2710aa0 100644 --- a/bin/servergen.js +++ b/bin/servergen.js @@ -27,7 +27,7 @@ const config = getConfig(__dirname, process.cwd()); program .version(pkg.version) - .option('-f, --framework ', 'Enter Name of Framework: Node | Express') + .option('-f, --framework ', 'Enter Name of Framework: Node | Express | Fastify') .requiredOption('-n, --name ', 'Enter Name of App') .option('-v --view ', 'Name of View Engine: Pug | Jade | EJS | HBS') .option('--db', 'Install Mongoose & the Folder Directory for it') From 04645d140fddefdce18072cf0524dd56f6f19679 Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 17:46:49 +0530 Subject: [PATCH 19/24] feat: add typescript dependency versions --- lib/constants.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/constants.js b/lib/constants.js index 569d4d8..27cbd59 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -46,4 +46,8 @@ export const DEPENDENCY_VERSIONS = { '@fastify/formbody': '^8.0.2', '@fastify/helmet': '^13.0.1', '@fastify/view': '^10.0.1', + typescript: '^5.7.3', + '@types/node': '^22.10.7', + 'ts-node': '^10.9.2', + 'tsx': '^4.19.2', }; From 03e0a3d2c79313d61af44e1c620deb886153f807 Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 17:46:58 +0530 Subject: [PATCH 20/24] feat: add fastify-ts template path --- lib/config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/config.js b/lib/config.js index 28ecb3d..857cefa 100644 --- a/lib/config.js +++ b/lib/config.js @@ -18,6 +18,7 @@ export const getConfig = (baseDir, cwd) => { express: path.join(baseDir, '..', 'templates', 'express'), node: path.join(baseDir, '..', 'templates', 'node'), fastify: path.join(baseDir, '..', 'templates', 'fastify'), + 'fastify-ts': path.join(baseDir, '..', 'templates', 'fastify-ts'), views: path.join(baseDir, '..', 'templates', 'express', 'views'), }, cwd, From aba94fc37eb0f5e85ab60011b83476540fd6c577 Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 17:47:06 +0530 Subject: [PATCH 21/24] feat: add fastify typescript templates --- templates/fastify-ts/.dockerignore | 11 +++ templates/fastify-ts/.env.example | 7 ++ templates/fastify-ts/.gitignore | 7 ++ templates/fastify-ts/Dockerfile | 39 ++++++++++ templates/fastify-ts/README.md | 85 +++++++++++++++++++++ templates/fastify-ts/src/config/mongoose.ts | 40 ++++++++++ templates/fastify-ts/src/index(config).ts | 81 ++++++++++++++++++++ templates/fastify-ts/src/index.ts | 77 +++++++++++++++++++ templates/fastify-ts/src/routes/index.ts | 59 ++++++++++++++ templates/fastify-ts/tsconfig.json | 20 +++++ 10 files changed, 426 insertions(+) create mode 100644 templates/fastify-ts/.dockerignore create mode 100644 templates/fastify-ts/.env.example create mode 100644 templates/fastify-ts/.gitignore create mode 100644 templates/fastify-ts/Dockerfile create mode 100644 templates/fastify-ts/README.md create mode 100644 templates/fastify-ts/src/config/mongoose.ts create mode 100644 templates/fastify-ts/src/index(config).ts create mode 100644 templates/fastify-ts/src/index.ts create mode 100644 templates/fastify-ts/src/routes/index.ts create mode 100644 templates/fastify-ts/tsconfig.json diff --git a/templates/fastify-ts/.dockerignore b/templates/fastify-ts/.dockerignore new file mode 100644 index 0000000..eb850c9 --- /dev/null +++ b/templates/fastify-ts/.dockerignore @@ -0,0 +1,11 @@ +node_modules +npm-debug.log +.git +.gitignore +.env +*.md +.DS_Store +coverage +.nyc_output +src +tsconfig.json diff --git a/templates/fastify-ts/.env.example b/templates/fastify-ts/.env.example new file mode 100644 index 0000000..a02675d --- /dev/null +++ b/templates/fastify-ts/.env.example @@ -0,0 +1,7 @@ +# Server Configuration +PORT=3000 +HOST=0.0.0.0 +NODE_ENV=development + +# Database Configuration (if using --db flag) +MONGODB_URI=mongodb://localhost:27017/servergen diff --git a/templates/fastify-ts/.gitignore b/templates/fastify-ts/.gitignore new file mode 100644 index 0000000..c3b459a --- /dev/null +++ b/templates/fastify-ts/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +.env +*.log +.DS_Store +coverage/ +.nyc_output/ +dist/ diff --git a/templates/fastify-ts/Dockerfile b/templates/fastify-ts/Dockerfile new file mode 100644 index 0000000..46ac9b6 --- /dev/null +++ b/templates/fastify-ts/Dockerfile @@ -0,0 +1,39 @@ +FROM node:20-alpine AS builder + +WORKDIR /app + +# Copy package files +COPY package*.json ./ +COPY tsconfig.json ./ + +# Install all dependencies (including dev for build) +RUN npm ci + +# Copy source code +COPY src ./src + +# Build TypeScript +RUN npm run build + +# Production stage +FROM node:20-alpine + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install production dependencies only +RUN npm ci --only=production + +# Copy built files from builder +COPY --from=builder /app/dist ./dist + +# Expose the application port +EXPOSE 3000 + +# Set environment to production +ENV NODE_ENV=production + +# Run the application +CMD ["node", "dist/index.js"] diff --git a/templates/fastify-ts/README.md b/templates/fastify-ts/README.md new file mode 100644 index 0000000..ae005ec --- /dev/null +++ b/templates/fastify-ts/README.md @@ -0,0 +1,85 @@ +# Fastify TypeScript Application + +A high-performance TypeScript web server built with [Fastify](https://fastify.dev/). + +## Features + +- Full TypeScript support with strict mode +- Fast JSON parsing and serialization +- Built-in schema validation with type inference +- Structured logging with Pino +- Security headers via @fastify/helmet +- CORS support +- Graceful shutdown handling +- Health check endpoint +- Multi-stage Docker build for optimized images + +## Getting Started + +### Development + +```bash +npm run dev +``` + +This uses `tsx` for fast TypeScript execution with hot reload. + +### Build + +```bash +npm run build +``` + +Compiles TypeScript to JavaScript in the `dist/` directory. + +### Production + +```bash +npm run build +NODE_ENV=production node dist/index.js +``` + +## API Endpoints + +- `GET /` - Welcome message +- `POST /` - Echo request body +- `GET /health` - Health check for container orchestration + +## Project Structure + +``` +src/ + index.ts # Application entry point + routes/ + index.ts # Route definitions + config/ + mongoose.ts # Database connection (if --db) + controllers/ # Request handlers + model/ # Data models +``` + +## Configuration + +Environment variables: + +| Variable | Default | Description | +|----------|---------|-------------| +| PORT | 3000 | Server port | +| HOST | 0.0.0.0 | Server host | +| NODE_ENV | development | Environment (development/production/test) | +| MONGODB_URI | mongodb://localhost:27017/servergen | MongoDB connection string | + +## Docker + +Build and run with Docker: + +```bash +docker build -t my-fastify-app . +docker run -p 3000:3000 my-fastify-app +``` + +The Dockerfile uses multi-stage build to create an optimized production image. + +## License + +MIT diff --git a/templates/fastify-ts/src/config/mongoose.ts b/templates/fastify-ts/src/config/mongoose.ts new file mode 100644 index 0000000..8fe3dd0 --- /dev/null +++ b/templates/fastify-ts/src/config/mongoose.ts @@ -0,0 +1,40 @@ +/** + * MongoDB connection plugin for Fastify. + * @description Connects to MongoDB using Mongoose and decorates the Fastify instance. + */ + +import fp from 'fastify-plugin'; +import mongoose from 'mongoose'; +import { FastifyInstance, FastifyPluginAsync } from 'fastify'; + +declare module 'fastify' { + interface FastifyInstance { + mongoose: typeof mongoose; + } +} + +/** + * Mongoose connection plugin. + */ +const mongooseConnector: FastifyPluginAsync = async (fastify: FastifyInstance): Promise => { + const uri = process.env.MONGODB_URI || 'mongodb://localhost:27017/servergen'; + + try { + await mongoose.connect(uri); + fastify.log.info('Connected to MongoDB'); + + // Decorate fastify with mongoose instance + fastify.decorate('mongoose', mongoose); + + // Close connection on fastify close + fastify.addHook('onClose', async (instance) => { + await mongoose.connection.close(); + instance.log.info('MongoDB connection closed'); + }); + } catch (err) { + fastify.log.error('MongoDB connection error:', err); + throw err; + } +}; + +export default fp(mongooseConnector); diff --git a/templates/fastify-ts/src/index(config).ts b/templates/fastify-ts/src/index(config).ts new file mode 100644 index 0000000..571dcca --- /dev/null +++ b/templates/fastify-ts/src/index(config).ts @@ -0,0 +1,81 @@ +/** + * Fastify application entry point with MongoDB configuration. + * @description High-performance TypeScript server with logging, security headers, and database. + */ + +import Fastify, { FastifyInstance } from 'fastify'; +import cors from '@fastify/cors'; +import formbody from '@fastify/formbody'; +import helmet from '@fastify/helmet'; +import mongooseConnector from './config/mongoose'; +import routes from './routes'; + +type Environment = 'development' | 'production' | 'test'; + +const envToLogger: Record = { + development: { + transport: { + target: 'pino-pretty', + options: { + translateTime: 'HH:MM:ss Z', + ignore: 'pid,hostname', + }, + }, + }, + production: true, + test: false, +}; + +const environment = (process.env.NODE_ENV || 'development') as Environment; + +const fastify: FastifyInstance = Fastify({ + logger: envToLogger[environment] ?? true, +}); + +// Register plugins +fastify.register(cors); +fastify.register(formbody); +fastify.register(helmet); + +// Views + +// Register database connector +fastify.register(mongooseConnector); + +// Register routes +fastify.register(routes); + +/** + * Health check endpoint for container orchestration. + */ +fastify.get('/health', async () => { + return { status: 'ok', timestamp: new Date().toISOString() }; +}); + +/** + * Graceful shutdown handler. + */ +const closeGracefully = async (signal: string): Promise => { + fastify.log.info(`Received signal: ${signal}`); + await fastify.close(); + process.exit(0); +}; + +process.on('SIGINT', () => closeGracefully('SIGINT')); +process.on('SIGTERM', () => closeGracefully('SIGTERM')); + +/** + * Start the server. + */ +const start = async (): Promise => { + try { + const port = parseInt(process.env.PORT || '3000', 10); + const host = process.env.HOST || '0.0.0.0'; + await fastify.listen({ port, host }); + } catch (err) { + fastify.log.error(err); + process.exit(1); + } +}; + +start(); diff --git a/templates/fastify-ts/src/index.ts b/templates/fastify-ts/src/index.ts new file mode 100644 index 0000000..a0eef0c --- /dev/null +++ b/templates/fastify-ts/src/index.ts @@ -0,0 +1,77 @@ +/** + * Fastify application entry point. + * @description High-performance TypeScript server with logging and security headers. + */ + +import Fastify, { FastifyInstance } from 'fastify'; +import cors from '@fastify/cors'; +import formbody from '@fastify/formbody'; +import helmet from '@fastify/helmet'; +import routes from './routes'; + +type Environment = 'development' | 'production' | 'test'; + +const envToLogger: Record = { + development: { + transport: { + target: 'pino-pretty', + options: { + translateTime: 'HH:MM:ss Z', + ignore: 'pid,hostname', + }, + }, + }, + production: true, + test: false, +}; + +const environment = (process.env.NODE_ENV || 'development') as Environment; + +const fastify: FastifyInstance = Fastify({ + logger: envToLogger[environment] ?? true, +}); + +// Register plugins +fastify.register(cors); +fastify.register(formbody); +fastify.register(helmet); + +// Views + +// Register routes +fastify.register(routes); + +/** + * Health check endpoint for container orchestration. + */ +fastify.get('/health', async () => { + return { status: 'ok', timestamp: new Date().toISOString() }; +}); + +/** + * Graceful shutdown handler. + */ +const closeGracefully = async (signal: string): Promise => { + fastify.log.info(`Received signal: ${signal}`); + await fastify.close(); + process.exit(0); +}; + +process.on('SIGINT', () => closeGracefully('SIGINT')); +process.on('SIGTERM', () => closeGracefully('SIGTERM')); + +/** + * Start the server. + */ +const start = async (): Promise => { + try { + const port = parseInt(process.env.PORT || '3000', 10); + const host = process.env.HOST || '0.0.0.0'; + await fastify.listen({ port, host }); + } catch (err) { + fastify.log.error(err); + process.exit(1); + } +}; + +start(); diff --git a/templates/fastify-ts/src/routes/index.ts b/templates/fastify-ts/src/routes/index.ts new file mode 100644 index 0000000..2042e91 --- /dev/null +++ b/templates/fastify-ts/src/routes/index.ts @@ -0,0 +1,59 @@ +/** + * Fastify routes plugin. + * @description Defines API routes for the application. + */ + +import { FastifyInstance, FastifyPluginAsync } from 'fastify'; + +/** + * Route plugin that registers all application routes. + */ +const routes: FastifyPluginAsync = async (fastify: FastifyInstance): Promise => { + /** + * GET / - Returns welcome message. + */ + fastify.get('/', { + schema: { + response: { + 200: { + type: 'object', + properties: { + message: { type: 'string' }, + }, + }, + }, + }, + }, async () => { + return { message: 'Welcome to ServerGen!' }; + }); + + /** + * POST / - Returns welcome message with request body. + */ + fastify.post<{ Body: { name?: string } }>('/', { + schema: { + body: { + type: 'object', + properties: { + name: { type: 'string' }, + }, + }, + response: { + 200: { + type: 'object', + properties: { + message: { type: 'string' }, + data: { type: 'object' }, + }, + }, + }, + }, + }, async (request) => { + return { + message: 'Welcome to ServerGen!', + data: request.body, + }; + }); +}; + +export default routes; diff --git a/templates/fastify-ts/tsconfig.json b/templates/fastify-ts/tsconfig.json new file mode 100644 index 0000000..c83c533 --- /dev/null +++ b/templates/fastify-ts/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} From 5de4bbcaaa4cb4ba2000433ed422b0fc81b88ab8 Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 17:47:18 +0530 Subject: [PATCH 22/24] feat: add typescript support to app generator --- lib/app_generator.js | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/lib/app_generator.js b/lib/app_generator.js index ff97c45..717dd6e 100644 --- a/lib/app_generator.js +++ b/lib/app_generator.js @@ -25,6 +25,7 @@ class AppGenerator { * @param {boolean} options.db - Whether to include database configuration. * @param {number} options.port - The port number for the app. * @param {boolean} options.skipInstall - Whether to skip npm install. + * @param {boolean} options.typescript - Whether to generate TypeScript project. * @param {Object} options.config - Configuration object from lib/config. * @param {Object} dependencies - Injected dependencies. * @param {Object} dependencies.fsHelper - File system helper module. @@ -39,6 +40,7 @@ class AppGenerator { this.db = options.db; this.port = options.port || 3000; this.skipInstall = options.skipInstall || false; + this.typescript = options.typescript || false; this.config = options.config; this.fsHelper = dependencies.fsHelper; @@ -60,6 +62,9 @@ class AppGenerator { case 'node': return this.config.paths.templates.node; case 'fastify': + if (this.typescript) { + return this.config.paths.templates['fastify-ts']; + } return this.config.paths.templates.fastify; default: return this.config.paths.templates.express; @@ -104,7 +109,7 @@ class AppGenerator { * Creates the application structure based on framework. */ createAppStructure() { - this.logger?.debug('Creating app structure', { framework: this.framework }); + this.logger?.debug('Creating app structure', { framework: this.framework, typescript: this.typescript }); if (this.framework === 'node') { this.fileCreator.createNodeApp( this.templatesDir, @@ -114,13 +119,23 @@ class AppGenerator { this.db ); } else if (this.framework === 'fastify') { - this.fileCreator.createFastifyApp( - this.templatesDir, - this.folderDir, - this.appName, - this.view, - this.db - ); + if (this.typescript) { + this.fileCreator.createFastifyTsApp( + this.templatesDir, + this.folderDir, + this.appName, + this.view, + this.db + ); + } else { + this.fileCreator.createFastifyApp( + this.templatesDir, + this.folderDir, + this.appName, + this.view, + this.db + ); + } } else { this.fileCreator.createExpressApp( this.templatesDir, @@ -138,12 +153,13 @@ class AppGenerator { configurePort() { if (this.port !== 3000) { this.logger?.debug('Configuring custom port', { port: this.port }); - const indexPath = path.join(this.folderDir, 'index.js'); + const indexFile = this.typescript ? 'src/index.ts' : 'index.js'; + const indexPath = path.join(this.folderDir, indexFile); try { let content = fs.readFileSync(indexPath, 'utf8'); content = content.replace( - /process\.env\.PORT \|\| 3000/g, - `process.env.PORT || ${this.port}` + /process\.env\.PORT \|\| (')?3000(')?/g, + `process.env.PORT || '${this.port}'` ); fs.writeFileSync(indexPath, content, 'utf8'); this.logger?.step(`Port configured to ${this.port}`); From b7b8146654a91831a2a67a360aeee1a4600a93db Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 17:47:30 +0530 Subject: [PATCH 23/24] feat: add typescript app creation in file generator --- lib/file_generator.js | 140 +++++++++++++++++++++++++++++++++++------- 1 file changed, 117 insertions(+), 23 deletions(-) diff --git a/lib/file_generator.js b/lib/file_generator.js index 8b3a39b..313b3a6 100644 --- a/lib/file_generator.js +++ b/lib/file_generator.js @@ -37,48 +37,66 @@ const generateMVC = (folderDir, templatesDir) => { * @param {string} appName - The application name. * @param {string|null} view - The view engine name (ejs, pug, jade, hbs). * @param {boolean} config - Whether to include mongoose configuration. - * @param {string} framework - The framework type ('node', 'express', or 'fastify'). + * @param {string} framework - The framework type ('node', 'express', 'fastify', or 'fastify-ts'). */ const generatePackage = (folderDir, appName, view, config, framework) => { + const isTypescript = framework === 'fastify-ts'; const pkg = { name: appName, version: '1.0.0', description: '', - main: 'index.js', - scripts: { - start: 'nodemon index.js', - }, - dependencies: { - nodemon: DEPENDENCY_VERSIONS.nodemon, - }, + main: isTypescript ? 'dist/index.js' : 'index.js', + scripts: isTypescript + ? { + dev: 'tsx watch src/index.ts', + build: 'tsc', + start: 'node dist/index.js', + } + : { + start: 'nodemon index.js', + }, + dependencies: {}, devDependencies: {}, }; + if (!isTypescript) { + pkg.dependencies.nodemon = DEPENDENCY_VERSIONS.nodemon; + } + if (framework === 'express') { pkg.dependencies.express = DEPENDENCY_VERSIONS.express; pkg.dependencies.cors = DEPENDENCY_VERSIONS.cors; - } else if (framework === 'fastify') { + } else if (framework === 'fastify' || framework === 'fastify-ts') { pkg.dependencies.fastify = DEPENDENCY_VERSIONS.fastify; pkg.dependencies['@fastify/cors'] = DEPENDENCY_VERSIONS['@fastify/cors']; pkg.dependencies['@fastify/formbody'] = DEPENDENCY_VERSIONS['@fastify/formbody']; pkg.dependencies['@fastify/helmet'] = DEPENDENCY_VERSIONS['@fastify/helmet']; pkg.devDependencies['pino-pretty'] = '^13.0.0'; + + if (isTypescript) { + pkg.devDependencies.typescript = DEPENDENCY_VERSIONS.typescript; + pkg.devDependencies['@types/node'] = DEPENDENCY_VERSIONS['@types/node']; + pkg.devDependencies.tsx = DEPENDENCY_VERSIONS.tsx; + } } else { pkg.dependencies.cors = DEPENDENCY_VERSIONS.cors; } if (view && VIEW_ENGINES[view]) { pkg.dependencies[view] = VIEW_ENGINES[view]; - if (framework === 'fastify') { + if (framework === 'fastify' || framework === 'fastify-ts') { pkg.dependencies['@fastify/view'] = DEPENDENCY_VERSIONS['@fastify/view']; } } if (config) { pkg.dependencies.mongoose = DEPENDENCY_VERSIONS.mongoose; - if (framework === 'fastify') { + if (framework === 'fastify' || framework === 'fastify-ts') { pkg.dependencies['fastify-plugin'] = '^5.0.1'; } + if (isTypescript) { + pkg.devDependencies['@types/mongoose'] = '^5.11.97'; + } } // Remove empty devDependencies @@ -140,6 +158,52 @@ const createFastifyApp = (templatesDir, folderDir, appName, view, config) => { console.log('Generating Fastify application..'); }; +/** + * Generates TypeScript MVC folder structure. + * @param {string} folderDir - The application directory path. + * @param {string} templatesDir - The templates directory path. + */ +const generateTsMVC = (folderDir, templatesDir) => { + const dirs = ['src/controllers', 'src/model', 'src/routes']; + + dirs.forEach((dir) => { + const dirPath = path.join(folderDir, dir); + fs.ensureDirSync(dirPath); + }); + + const routesPath = path.join(templatesDir, 'src', 'routes', 'index.ts'); + if (fs.existsSync(routesPath)) { + fs_helper.buildFilewithContents( + routesPath, + path.join(folderDir, 'src', 'routes'), + 'index.ts' + ); + } +}; + +/** + * Creates a Fastify TypeScript application. + * @param {string} templatesDir - Path to Fastify TypeScript templates. + * @param {string} folderDir - The application directory path. + * @param {string} appName - The application name. + * @param {string|null} view - The view engine name. + * @param {boolean} config - Whether to include mongoose configuration. + */ +const createFastifyTsApp = (templatesDir, folderDir, appName, view, config) => { + // Copy main index.ts + const basePath = path.join(templatesDir, 'src', 'index.ts'); + fs.ensureDirSync(path.join(folderDir, 'src')); + fs_helper.buildFilewithContents(basePath, path.join(folderDir, 'src'), 'index.ts'); + + // Copy tsconfig.json + const tsconfigPath = path.join(templatesDir, 'tsconfig.json'); + fs_helper.buildFilewithContents(tsconfigPath, folderDir, 'tsconfig.json'); + + generateTsMVC(folderDir, templatesDir); + generatePackage(folderDir, appName, view, config, 'fastify-ts'); + console.log('Generating Fastify TypeScript application..'); +}; + /** * Handles view engine setup for the application. * @param {string} folderDir - The application directory path. @@ -176,20 +240,33 @@ const handleViews = (folderDir, viewsDir, view) => { * @param {boolean} addView - Whether to add or remove view configuration. */ const manageViews = (folderPath, viewName, addView) => { + // Check for TypeScript file first + const tsFile = path.join(folderPath, 'src', 'index.ts'); const jsFile = path.join(folderPath, 'index.js'); + const isTypeScript = fs.existsSync(tsFile); + const targetFile = isTypeScript ? tsFile : jsFile; try { - let data = fs.readFileSync(jsFile, 'utf8'); + let data = fs.readFileSync(targetFile, 'utf8'); const isFastify = data.includes('Fastify'); if (addView) { let viewString; if (isFastify) { - viewString = `const path = require('path'); + if (isTypeScript) { + viewString = `import path from 'path'; +import view from '@fastify/view'; +fastify.register(view, { + engine: { ${viewName}: require('${viewName}') }, + root: path.join(__dirname, '..', 'views'), +});`; + } else { + viewString = `const path = require('path'); fastify.register(require('@fastify/view'), { engine: { ${viewName}: require('${viewName}') }, root: path.join(__dirname, 'views'), });`; + } } else { viewString = `app.set('view engine','${viewName}') \n app.set('views', path.join(__dirname, 'views'));`; } @@ -197,7 +274,7 @@ fastify.register(require('@fastify/view'), { } else { data = data.replace('// Views', ''); } - fs.writeFileSync(jsFile, data, 'utf8'); + fs.writeFileSync(targetFile, data, 'utf8'); } catch (err) { console.log(err); } @@ -211,15 +288,31 @@ fastify.register(require('@fastify/view'), { */ const handleConfig = (folderDir, templatesDir, framework) => { console.log('Configuring Mongoose..'); - fs_helper.createDir(folderDir, 'config'); - const basePath = path.join(templatesDir, 'config', 'mongoose.js'); - fs_helper.buildFilewithContents( - basePath, - path.join(folderDir, 'config'), - 'mongoose.js' - ); - const newIndexPath = path.join(templatesDir, 'index(config).js'); - fs_helper.buildFilewithContents(newIndexPath, folderDir, 'index.js'); + + // Check if TypeScript project + const isTypeScript = fs.existsSync(path.join(folderDir, 'src')); + + if (isTypeScript) { + fs_helper.createDir(path.join(folderDir, 'src'), 'config'); + const basePath = path.join(templatesDir, 'src', 'config', 'mongoose.ts'); + fs_helper.buildFilewithContents( + basePath, + path.join(folderDir, 'src', 'config'), + 'mongoose.ts' + ); + const newIndexPath = path.join(templatesDir, 'src', 'index(config).ts'); + fs_helper.buildFilewithContents(newIndexPath, path.join(folderDir, 'src'), 'index.ts'); + } else { + fs_helper.createDir(folderDir, 'config'); + const basePath = path.join(templatesDir, 'config', 'mongoose.js'); + fs_helper.buildFilewithContents( + basePath, + path.join(folderDir, 'config'), + 'mongoose.js' + ); + const newIndexPath = path.join(templatesDir, 'index(config).js'); + fs_helper.buildFilewithContents(newIndexPath, folderDir, 'index.js'); + } }; /** @@ -273,6 +366,7 @@ export { createExpressApp, createNodeApp, createFastifyApp, + createFastifyTsApp, handleViews, handleConfig, addGitIgnore, From 6072324e70a7ff477794e011e13dc8970848946f Mon Sep 17 00:00:00 2001 From: Keshav Malik Date: Wed, 28 Jan 2026 17:47:55 +0530 Subject: [PATCH 24/24] feat: add --typescript cli flag --- bin/servergen.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bin/servergen.js b/bin/servergen.js index 2710aa0..431944c 100644 --- a/bin/servergen.js +++ b/bin/servergen.js @@ -32,6 +32,7 @@ program .option('-v --view ', 'Name of View Engine: Pug | Jade | EJS | HBS') .option('--db', 'Install Mongoose & the Folder Directory for it') .option('-p, --port ', 'Set default port for the app', '3000') + .option('--typescript', 'Generate TypeScript project (Fastify only)') .option('--skip-install', 'Skip npm install step') .option('--debug', 'Enable debug logging') .parse(process.argv); @@ -50,8 +51,9 @@ handleValidationErrors(validationResult); const appName = fileName.cleanAppName(options.name); const port = parseInt(options.port, 10) || 3000; const skipInstall = options.skipInstall || false; +const typescript = options.typescript || false; -logger.debug('Parsed configuration', { appName, port, framework: options.framework, skipInstall }); +logger.debug('Parsed configuration', { appName, port, framework: options.framework, skipInstall, typescript }); /** * Main function to run the application generator. @@ -65,6 +67,7 @@ const main = async () => { db: options.db, port, skipInstall, + typescript, config, }, {