From 4067774f6af0c77dfbcf7f770aa8e18af5783e6c Mon Sep 17 00:00:00 2001 From: RulerChen Date: Sun, 16 Jun 2024 17:57:07 +0800 Subject: [PATCH] feat: api doc --- packages/index.js | 6 +- packages/package.json | 2 +- packages/scripts/createDoc.js | 14 ++++ packages/scripts/createPackageJson.js | 79 +++++++++---------- packages/scripts/createProject.js | 17 ++-- packages/scripts/index.js | 3 +- packages/scripts/printDocs.js | 12 +++ packages/scripts/printUnitTest.js | 2 +- packages/templates/package.json | 10 +-- .../templates/swagger/javascript/src/index.js | 26 ++++++ .../templates/swagger/javascript/swagger.js | 17 ++++ .../templates/swagger/typescript/src/index.ts | 27 +++++++ .../templates/swagger/typescript/swagger.ts | 18 +++++ packages/templates/tsconfig.json | 3 +- 14 files changed, 179 insertions(+), 57 deletions(-) create mode 100644 packages/scripts/createDoc.js create mode 100644 packages/scripts/printDocs.js create mode 100644 packages/templates/swagger/javascript/src/index.js create mode 100644 packages/templates/swagger/javascript/swagger.js create mode 100644 packages/templates/swagger/typescript/src/index.ts create mode 100644 packages/templates/swagger/typescript/swagger.ts diff --git a/packages/index.js b/packages/index.js index e137013..1a41ee3 100644 --- a/packages/index.js +++ b/packages/index.js @@ -8,6 +8,7 @@ import printProjectName from './scripts/printProjectName.js'; import printTemplate from './scripts/printTemplate.js'; import printLinter from './scripts/printLinter.js'; import printUnitTest from './scripts/printUnitTest.js'; +import printDocs from './scripts/printDocs.js'; import printProjectManager from './scripts/printProjectManager.js'; import createProject from './scripts/createProject.js'; import isProjectExist from './utils/isProjectExist.js'; @@ -25,10 +26,13 @@ main(async (program) => { let template = program.opts()['template'] || (await printTemplate()); let linter = program.opts()['linter'] || (await printLinter()); let unitTest = program.opts()['unitTest'] || (await printUnitTest()); + let apiDoc = program.opts()['apiDoc'] || (await printDocs()); let projectManager = program.opts()['manager'] || (await printProjectManager()); + // console.log(template, linter, unitTest, apiDoc, projectManager); + try { - await createProject({ projectName, template, projectManager, linter, unitTest }); + await createProject({ projectName, template, projectManager, linter, unitTest, apiDoc }); } catch (error) { console.log(error); } diff --git a/packages/package.json b/packages/package.json index cee57b9..464d9ab 100644 --- a/packages/package.json +++ b/packages/package.json @@ -1,6 +1,6 @@ { "name": "gen-express-cli", - "version": "1.0.3", + "version": "1.0.4", "description": "A simple express cli to generate express app simply by running a command", "main": "index.js", "type": "module", diff --git a/packages/scripts/createDoc.js b/packages/scripts/createDoc.js new file mode 100644 index 0000000..0b3bbfe --- /dev/null +++ b/packages/scripts/createDoc.js @@ -0,0 +1,14 @@ +import fsPromises from 'fs/promises'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +export async function createDoc(projectName, template) { + const SOURCE_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), '../', `templates/swagger/${template}`); + const TARGET_PATH = path.join(process.cwd(), projectName); + + try { + await fsPromises.cp(SOURCE_PATH, TARGET_PATH, { recursive: true, force: true }); + } catch (error) { + throw error; + } +} diff --git a/packages/scripts/createPackageJson.js b/packages/scripts/createPackageJson.js index d391007..d24a3ca 100644 --- a/packages/scripts/createPackageJson.js +++ b/packages/scripts/createPackageJson.js @@ -2,20 +2,13 @@ import fsPromises from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; -export async function createPackageJson(projectName, template, linter, unitTest) { +export async function createPackageJson(projectName, template, linter, unitTest, apiDoc) { const __dirname = path.dirname(fileURLToPath(import.meta.url)); const packageJsonTemplate = JSON.parse(await fsPromises.readFile(path.resolve(__dirname, `../templates/package.json`), 'utf-8')); const packageJson = { ...packageJsonTemplate, name: projectName, - scripts: {}, - dependencies: { - ...packageJsonTemplate.dependencies, - }, - devDependencies: { - ...packageJsonTemplate.devDependencies, - }, }; if (template === 'javascript') { @@ -24,22 +17,24 @@ export async function createPackageJson(projectName, template, linter, unitTest) packageJson.scripts['start'] = 'cross-env NODE_ENV=production node ./src/index.js'; if (linter) { packageJson.scripts['lint'] = 'eslint ./src/**/*'; - packageJson.devDependencies['eslint'] = '~8.56.0'; - packageJson.devDependencies['eslint-config-standard'] = '~17.1.0'; - packageJson.devDependencies['eslint-plugin-import'] = '~2.29.1'; - packageJson.devDependencies['eslint-plugin-prettier'] = '~5.1.3'; - packageJson.devDependencies['eslint-config-prettier'] = '~9.1.0'; - packageJson.devDependencies['prettier'] = '~3.2.5'; + packageJson.scripts['format'] = 'prettier --write ./**/*.{js,json}'; + packageJson.devDependencies['eslint'] = '^8'; + packageJson.devDependencies['eslint-config-standard'] = '^17'; + packageJson.devDependencies['eslint-plugin-import'] = '^2'; + packageJson.devDependencies['eslint-plugin-prettier'] = '^5'; + packageJson.devDependencies['eslint-config-prettier'] = '^9'; + packageJson.devDependencies['prettier'] = '^3'; } if (unitTest === 'jest') { - if (linter) packageJson.devDependencies['eslint-plugin-jest'] = '~27.6.3'; + if (linter) packageJson.devDependencies['eslint-plugin-jest'] = '^27'; packageJson.scripts['test'] = 'jest --coverage=true -w=1 --forceExit --detectOpenHandles --watchAll=false --testPathPattern=src/__tests__'; - packageJson.devDependencies['jest'] = '~29.7.0'; + packageJson.devDependencies['jest'] = '^29'; } - if (e2eTest === 'supertest') { - packageJson.scripts['test:e2e'] = 'node --experimental-vm-modules node_modules/jest/bin/jest.js --testPathPattern=src/e2e'; - packageJson.devDependencies['supertest'] = '~6.3.4'; + if (apiDoc) { + packageJson.scripts['swagger'] = 'node swagger.js'; + packageJson.devDependencies['swagger-autogen'] = '^2'; + packageJson.devDependencies['swagger-ui-express'] = '^5'; } } @@ -47,35 +42,37 @@ export async function createPackageJson(projectName, template, linter, unitTest) packageJson.scripts['dev'] = 'cross-env NODE_ENV=development nodemon --exec ts-node ./src/index.ts'; packageJson.scripts['build'] = 'tsc'; packageJson.scripts['start'] = 'cross-env NODE_ENV=production node ./build/index.js'; - packageJson.devDependencies['typescript'] = '~5.3.3'; - packageJson.devDependencies['ts-node'] = '~10.9.2'; - packageJson.devDependencies['@types/cors'] = '~2.8.17'; - packageJson.devDependencies['@types/express'] = '~4.17.21'; - packageJson.devDependencies['@types/node'] = '~20.11.16'; + packageJson.devDependencies['typescript'] = '^5'; + packageJson.devDependencies['ts-node'] = '^10'; + packageJson.devDependencies['@types/cors'] = '^2'; + packageJson.devDependencies['@types/express'] = '^4'; + packageJson.devDependencies['@types/node'] = '^20'; if (linter) { packageJson.scripts['lint'] = 'eslint ./src/**/*'; - packageJson.devDependencies['eslint'] = '~8.56.0'; - packageJson.devDependencies['@typescript-eslint/eslint-plugin'] = '~6.20.0'; - packageJson.devDependencies['eslint-config-prettier'] = '~9.1.0'; - packageJson.devDependencies['eslint-config-standard-with-typescript'] = '~43.0.1'; - packageJson.devDependencies['eslint-plugin-import'] = '~2.29.1'; - packageJson.devDependencies['eslint-plugin-n'] = '~16.6.2'; - packageJson.devDependencies['eslint-plugin-prettier'] = '~5.1.3'; - packageJson.devDependencies['eslint-plugin-promise'] = '~6.1.1'; - packageJson.devDependencies['prettier'] = '~3.2.5'; + packageJson.scripts['format'] = 'prettier --write ./**/*.{ts,json}'; + packageJson.devDependencies['eslint'] = '^8'; + packageJson.devDependencies['@typescript-eslint/eslint-plugin'] = '^6'; + packageJson.devDependencies['eslint-config-prettier'] = '^9'; + packageJson.devDependencies['eslint-config-standard-with-typescript'] = '^43'; + packageJson.devDependencies['eslint-plugin-import'] = '^2'; + packageJson.devDependencies['eslint-plugin-n'] = '^16'; + packageJson.devDependencies['eslint-plugin-prettier'] = '^5'; + packageJson.devDependencies['eslint-plugin-promise'] = '^6'; + packageJson.devDependencies['prettier'] = '^3'; } if (unitTest === 'jest') { - if (linter) packageJson.devDependencies['eslint-plugin-jest'] = '~27.6.3'; + if (linter) packageJson.devDependencies['eslint-plugin-jest'] = '^27'; packageJson.scripts['test'] = 'jest --coverage=true -w=1 --forceExit --detectOpenHandles --watchAll=false --testPathPattern=src/__tests__'; - packageJson.devDependencies['jest'] = '~29.7.0'; - packageJson.devDependencies['ts-jest'] = '~29.1.2'; - packageJson.devDependencies['@types/jest'] = '~29.5.12'; + packageJson.devDependencies['jest'] = '^29'; + packageJson.devDependencies['ts-jest'] = '^29'; + packageJson.devDependencies['@types/jest'] = '^29'; } - if (e2eTest === 'supertest') { - packageJson.scripts['test:e2e'] = 'node --experimental-vm-modules node_modules/jest/bin/jest.js --testPathPattern=src/e2e'; - packageJson.devDependencies['supertest'] = '~6.3.4'; - packageJson.devDependencies['@types/supertest'] = '~6.0.2'; + if (apiDoc) { + packageJson.scripts['swagger'] = 'ts-node swagger.ts'; + packageJson.devDependencies['swagger-autogen'] = '^2'; + packageJson.devDependencies['swagger-ui-express'] = '^5'; + packageJson.devDependencies['@types/swagger-ui-express'] = '^4'; } } diff --git a/packages/scripts/createProject.js b/packages/scripts/createProject.js index b3c0525..3b2f647 100644 --- a/packages/scripts/createProject.js +++ b/packages/scripts/createProject.js @@ -5,38 +5,43 @@ import { createLinter } from './createLinter.js'; import { createPackageJson } from './createPackageJson.js'; import { createTsConfig } from './createTsConfig.js'; import { createUnitTest } from './createUnitTest.js'; +import { createDoc } from './createDoc.js'; import { runCommand } from '../utils/exec.js'; -async function createStructure({ projectName, template, linter, unitTest }) { +async function createStructure({ projectName, template, linter, unitTest, apiDoc }) { try { await createBase(projectName, template); - await createPackageJson(projectName, template, linter, unitTest); + await createPackageJson(projectName, template, linter, unitTest, apiDoc); if (linter) await createLinter(projectName, template, unitTest); if (template === 'typescript') await createTsConfig(projectName, unitTest); if (unitTest === 'jest') await createUnitTest(projectName, template, unitTest); + if (apiDoc) await createDoc(projectName, template); } catch (error) { throw error; } } -async function installDependencies(projectName, projectManager) { +async function installDependencies(projectName, projectManager, linter) { if (projectManager === 'npm') { await runCommand(`cd ${projectName} && npm install && cd ..`); + if (linter) await runCommand(`cd ${projectName} && npm run format && cd ..`); } else if (projectManager === 'yarn') { await runCommand(`cd ${projectName} && yarn && cd ..`); + if (linter) await runCommand(`cd ${projectName} && yarn format && cd ..`); } else if (projectManager === 'pnpm') { await runCommand(`cd ${projectName} && pnpm install && cd ..`); + if (linter) await runCommand(`cd ${projectName} && pnpm run format && cd ..`); } } -export default async function createProject({ projectName, template, projectManager, linter, unitTest }) { +export default async function createProject({ projectName, template, projectManager, linter, unitTest, apiDoc }) { const spinner = createSpinner('Creating project...'); try { spinner.start(); - await createStructure({ projectName, template, linter, unitTest }); - await installDependencies(projectName, projectManager); + await createStructure({ projectName, template, linter, unitTest, apiDoc }); + await installDependencies(projectName, projectManager, linter); spinner.success({ text: 'Project created successfully' }); } catch (error) { diff --git a/packages/scripts/index.js b/packages/scripts/index.js index 147e329..911682f 100644 --- a/packages/scripts/index.js +++ b/packages/scripts/index.js @@ -10,8 +10,9 @@ export default function main(callback) { .version(APP_VERSION, '-v, --version', 'output the current version') .arguments('[project-name]', 'project name') .addOption(new Option('-t, --template ', 'choose express template').choices(Object.values(TEMPLATES))) - .addOption(new Option('-l, --linter ', 'choose linter').choices(['eslint', 'none'])) + .addOption(new Option('-l, --linter ', 'choose linter').default(false)) .addOption(new Option('-u, --unit-test ', 'choose unit test').choices(Object.values(UNIT_TEST))) + .addOption(new Option('-a, --api-doc', 'use swagger for API documentation').default(false)) .action(() => callback(program)); program.parse(process.argv); diff --git a/packages/scripts/printDocs.js b/packages/scripts/printDocs.js new file mode 100644 index 0000000..daadd9e --- /dev/null +++ b/packages/scripts/printDocs.js @@ -0,0 +1,12 @@ +import inquirer from 'inquirer'; + +export default async function printDocs() { + const iq = await inquirer.prompt({ + name: 'apiDoc', + type: 'confirm', + message: 'Do you want to use swagger for API documentation?', + default: false, + }); + + return iq.apiDoc; +} diff --git a/packages/scripts/printUnitTest.js b/packages/scripts/printUnitTest.js index 9215235..70d7f82 100644 --- a/packages/scripts/printUnitTest.js +++ b/packages/scripts/printUnitTest.js @@ -1,7 +1,7 @@ import inquirer from 'inquirer'; import { UNIT_TEST } from '../variables/templates.js'; -export default async function printProjectManager() { +export default async function printUnitTest() { const iq = await inquirer.prompt({ name: 'unitTest', type: 'list', diff --git a/packages/templates/package.json b/packages/templates/package.json index c3b3f8b..af0dbe0 100644 --- a/packages/templates/package.json +++ b/packages/templates/package.json @@ -11,12 +11,12 @@ "author": "", "license": "ISC", "dependencies": { - "cors": "~2.8.5", - "dotenv": "~16.4.1", - "express": "~4.18.2" + "cors": "^2", + "dotenv": "^16", + "express": "^4" }, "devDependencies": { - "cross-env": "~7.0.3", - "nodemon": "~3.0.3" + "cross-env": "^7", + "nodemon": "^3" } } diff --git a/packages/templates/swagger/javascript/src/index.js b/packages/templates/swagger/javascript/src/index.js new file mode 100644 index 0000000..afd640f --- /dev/null +++ b/packages/templates/swagger/javascript/src/index.js @@ -0,0 +1,26 @@ +import express from 'express'; +import dotenv from 'dotenv'; +import cors from 'cors'; + +import swaggerUi from 'swagger-ui-express'; +import swaggerDocument from '../apidoc.json'; + +import routes from './routes/index'; + +dotenv.config(); + +const app = express(); +const PORT = process.env.PORT ?? 8000; + +app.use(cors()); +app.use(express.urlencoded({ extended: true })); +app.use(express.json()); + +app.use(`/api`, routes); +app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); + +app.listen(PORT, () => { + console.log(`Server is running on port ${PORT}`); +}); + +export default app; diff --git a/packages/templates/swagger/javascript/swagger.js b/packages/templates/swagger/javascript/swagger.js new file mode 100644 index 0000000..7744033 --- /dev/null +++ b/packages/templates/swagger/javascript/swagger.js @@ -0,0 +1,17 @@ +import swaggerAutogen from 'swagger-autogen'; + +const doc = { + info: { + title: 'Gen Express CLI', + description: 'Gen Express CLI API Documentation', + }, + host: 'localhost:8000/api', +}; + +const outputFile = './apidoc.json'; +const routes = ['./src/routes/index.js']; + +/* NOTE: If you are using the express Router, you must pass in the 'routes' only the +root file where the route starts, such as index.js, app.js, routes.js, etc ... */ + +swaggerAutogen()(outputFile, routes, doc); diff --git a/packages/templates/swagger/typescript/src/index.ts b/packages/templates/swagger/typescript/src/index.ts new file mode 100644 index 0000000..45d2d3e --- /dev/null +++ b/packages/templates/swagger/typescript/src/index.ts @@ -0,0 +1,27 @@ +import express from 'express'; +import dotenv from 'dotenv'; +import cors from 'cors'; + +import swaggerUi from 'swagger-ui-express'; +import swaggerDocument from '../apidoc.json'; + +import routes from './routes/index'; + +dotenv.config(); + +const app = express(); +const PORT = process.env.PORT ?? 8000; + +app.use(cors()); +app.use(express.urlencoded({ extended: true })); +app.use(express.json()); + +app.use(`/api`, routes); +// eslint-disable-next-line @typescript-eslint/no-unsafe-argument +app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); + +app.listen(PORT, () => { + console.log(`Server is running on port ${PORT}`); +}); + +export default app; diff --git a/packages/templates/swagger/typescript/swagger.ts b/packages/templates/swagger/typescript/swagger.ts new file mode 100644 index 0000000..7ea87c7 --- /dev/null +++ b/packages/templates/swagger/typescript/swagger.ts @@ -0,0 +1,18 @@ +import swaggerAutogen from 'swagger-autogen'; + +const doc = { + info: { + title: 'Gen Express CLI', + description: 'Gen Express CLI API Documentation', + }, + host: 'localhost:8000/api', +}; + +const outputFile = './apidoc.json'; +const routes = ['./src/routes/index.ts']; + +/* NOTE: If you are using the express Router, you must pass in the 'routes' only the +root file where the route starts, such as index.js, app.js, routes.js, etc ... */ + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +swaggerAutogen()(outputFile, routes, doc); diff --git a/packages/templates/tsconfig.json b/packages/templates/tsconfig.json index b011688..2c31121 100644 --- a/packages/templates/tsconfig.json +++ b/packages/templates/tsconfig.json @@ -3,6 +3,7 @@ "target": "es6", "module": "NodeNext", "moduleResolution": "NodeNext", + "resolveJsonModule": true, "baseUrl": "src", "outDir": "build", "sourceMap": true, @@ -10,6 +11,6 @@ "esModuleInterop": true, "types": ["node"] }, - "include": ["process.env.d.ts", "./**/*.ts"], + "include": ["process.env.d.ts", "./**/*.ts", "swagger/javascript/swagger.js"], "exclude": ["node_modules"] }