ts5deco-express-controller
Version:
TypeScript 5 Modern Decorator Express Controller Framework
291 lines (273 loc) • 13.2 kB
JavaScript
#!/usr/bin/env node
#!/usr/bin/env node
"use strict";var E=Object.create;var u=Object.defineProperty;var U=Object.getOwnPropertyDescriptor;var I=Object.getOwnPropertyNames;var k=Object.getPrototypeOf,A=Object.prototype.hasOwnProperty;var S=(t,e)=>()=>(t&&(e=t(t=0)),e);var $=(t,e)=>{for(var s in e)u(t,s,{get:e[s],enumerable:!0})},O=(t,e,s,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of I(e))!A.call(t,o)&&o!==s&&u(t,o,{get:()=>e[o],enumerable:!(a=U(e,o))||a.enumerable});return t};var h=(t,e,s)=>(s=t!=null?E(k(t)):{},O(e||!t||!t.__esModule?u(s,"default",{value:t,enumerable:!0}):s,t));var T={};$(T,{initProject:()=>D});async function D(t="."){let e=c.default.resolve(t),s=c.default.join(e,"api");await l.default.mkdir(s,{recursive:!0}),await l.default.writeFile(c.default.join(s,"openapi.yaml"),`openapi: 3.0.0
info:
title: My API
version: 1.0.0
description: API for my application
servers:
- url: http://localhost:3000/api
description: Local development server
paths:
/users:
get:
summary: Get all users
operationId: getUsers
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: limit
in: query
schema:
type: integer
default: 10
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedUserResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
post:
summary: Create a new user
operationId: createUser
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
'201':
description: User created successfully
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/users/{id}:
get:
summary: Get a user by ID
operationId: getUserById
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
description: User not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
User:
type: object
required:
- id
- name
- email
properties:
id:
type: string
example: "123"
name:
type: string
example: "John Doe"
email:
type: string
format: email
example: "john@example.com"
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
CreateUserRequest:
type: object
required:
- name
- email
- password
properties:
name:
type: string
example: "John Doe"
email:
type: string
format: email
example: "john@example.com"
password:
type: string
format: password
minLength: 8
Error:
type: object
required:
- error
- message
properties:
error:
type: string
example: "NOT_FOUND"
message:
type: string
example: "The requested resource was not found"
code:
type: string
example: "USER_NOT_FOUND"
statusCode:
type: integer
example: 404
PaginatedUserResponse:
type: object
required:
- data
- total
- page
- limit
- hasNext
- hasPrev
properties:
data:
type: array
items:
$ref: '#/components/schemas/User'
total:
type: integer
example: 100
page:
type: integer
example: 1
limit:
type: integer
example: 10
hasNext:
type: boolean
example: true
hasPrev:
type: boolean
example: false
`);let o=c.default.join(e,"package.json"),n=!1;try{let r=JSON.parse(await l.default.readFile(o,"utf8"));r.scripts||(r.scripts={}),r.scripts["generate:types"]||(r.scripts["generate:types"]="ts5deco-express-controller generate",n=!0),r.scripts.generate||(r.scripts.generate="npm run generate:types",n=!0),n&&(await l.default.writeFile(o,JSON.stringify(r,null,2)),console.log("\u2705 Updated package.json with recommended scripts"))}catch{console.log("\u{1F4A1} Recommended package.json scripts:"),console.log(' "generate:types": "ts5deco-express-controller generate"'),console.log(' "generate": "npm run generate:types"')}try{let r=c.default.join(e,".gitignore"),d="";try{d=await l.default.readFile(r,"utf8")}catch{}d.includes("src/types/generated/")||(d+=`
# Generated OpenAPI types
src/types/generated/
`,await l.default.writeFile(r,d),console.log("\u2705 Updated .gitignore to exclude generated types"))}catch{console.log("\u{1F4A1} Recommended .gitignore addition:"),console.log(" src/types/generated/")}console.log(`
\u{1F4C1} Created files:`),console.log(` ${c.default.relative(process.cwd(),c.default.join(s,"openapi.yaml"))}`)}var l,c,w=S(()=>{"use strict";l=h(require("fs/promises")),c=h(require("path"))});var m=require("commander");var p=h(require("fs/promises")),i=h(require("path")),f=require("child_process");async function x(t){let{input:e,outputDir:s,apiTypesPath:a,utilsPath:o,generateAliases:n=!0,generateUtils:r=!0,openapiTypescriptOptions:d=[]}=t;try{await p.default.access(e)}catch{throw new Error(`OpenAPI specification file not found: ${e}`)}await p.default.mkdir(s,{recursive:!0});let y=i.default.join(s,"api.d.ts");console.log(`\u{1F4C4} Processing ${e}...`);let j=["npx openapi-typescript",`"${e}"`,"-o",`"${y}"`,...d].join(" ");try{(0,f.execSync)(j,{stdio:"inherit"}),console.log(`\u2705 Generated types: ${y}`)}catch(g){throw new Error(`Failed to generate types: ${g instanceof Error?g.message:String(g)}`)}let v=await R(y);r&&o&&(await b(o,y),console.log(`\u2705 Generated utility types: ${o}`)),n&&a&&(await q(a,v,o),console.log(`\u2705 Generated API type aliases: ${a}`))}async function R(t){try{let s=(await p.default.readFile(t,"utf8")).match(/schemas:\s*{([^}]+)}/s);if(!s||!s[1])return[];let a=s[1],o=[],n=/(\w+):\s*{/g,r;for(;(r=n.exec(a))!==null;)r[1]&&o.push(r[1]);return o}catch(e){return console.warn("Warning: Could not extract schema names:",e instanceof Error?e.message:String(e)),[]}}async function b(t,e){let a=`/**
* OpenAPI \uD0C0\uC785 \uC720\uD2F8\uB9AC\uD2F0
* openapi-typescript\uB85C \uC0DD\uC131\uB41C \uD0C0\uC785\uB4E4\uC744 \uC27D\uAC8C \uC0AC\uC6A9\uD558\uAE30 \uC704\uD55C \uD5EC\uD37C\uB4E4
*/
import type { paths, components } from './${i.default.relative(i.default.dirname(t),e).replace(/\.d\.ts$/,"").replace(/\\/g,"/")}';
/**
* OpenAPI \uC2A4\uD0A4\uB9C8\uC5D0\uC11C \uD0C0\uC785\uC744 \uCD94\uCD9C\uD558\uB294 \uD5EC\uD37C \uD0C0\uC785
* @example
* \`\`\`typescript
* type User = ExtractSchema<'User'>;
* type ErrorResponse = ExtractSchema<'Error'>;
* \`\`\`
*/
export type ExtractSchema<T extends keyof components['schemas']> =
components['schemas'][T];
/**
* OpenAPI \uACBD\uB85C\uC5D0\uC11C \uC751\uB2F5 \uD0C0\uC785\uC744 \uCD94\uCD9C\uD558\uB294 \uD5EC\uD37C \uD0C0\uC785
* @example
* \`\`\`typescript
* type GetUserResponse = ExtractResponse<'/users/{id}', 'get'>;
* type CreateUserResponse = ExtractResponse<'/users', 'post', 201>;
* \`\`\`
*/
export type ExtractResponse<
TPath extends keyof paths,
TMethod extends keyof paths[TPath],
TStatus extends number = 200
> = TPath extends keyof paths
? TMethod extends keyof paths[TPath]
? paths[TPath][TMethod] extends { responses: any }
? TStatus extends keyof paths[TPath][TMethod]['responses']
? paths[TPath][TMethod]['responses'][TStatus] extends {
content: { 'application/json': infer T }
}
? T
: never
: never
: never
: never
: never;
/**
* OpenAPI \uACBD\uB85C\uC5D0\uC11C \uC694\uCCAD \uBCF8\uBB38 \uD0C0\uC785\uC744 \uCD94\uCD9C\uD558\uB294 \uD5EC\uD37C \uD0C0\uC785
*/
export type ExtractRequestBody<
TPath extends keyof paths,
TMethod extends keyof paths[TPath]
> = TPath extends keyof paths
? TMethod extends keyof paths[TPath]
? paths[TPath][TMethod] extends {
requestBody: { content: { 'application/json': infer T } }
}
? T
: never
: never
: never;
/**
* OpenAPI \uACBD\uB85C\uC5D0\uC11C \uD30C\uB77C\uBBF8\uD130 \uD0C0\uC785\uC744 \uCD94\uCD9C\uD558\uB294 \uD5EC\uD37C \uD0C0\uC785
*/
export type ExtractParameters<
TPath extends keyof paths,
TMethod extends keyof paths[TPath]
> = TPath extends keyof paths
? TMethod extends keyof paths[TPath]
? paths[TPath][TMethod] extends { parameters: infer P }
? P
: never
: never
: never;
// \uC6D0\uBCF8 \uD0C0\uC785\uB4E4\uB3C4 re-export
export type { paths, components };
`;await p.default.mkdir(i.default.dirname(t),{recursive:!0}),await p.default.writeFile(t,a)}async function q(t,e,s){let o=`/**
* API \uD0C0\uC785 \uBCC4\uCE6D
* OpenAPI \uC2A4\uD399\uC5D0\uC11C \uC0DD\uC131\uB41C \uD0C0\uC785\uB4E4\uC744 \uC27D\uAC8C \uC0AC\uC6A9\uD558\uAE30 \uC704\uD55C \uBCC4\uCE6D \uC815\uC758
*
* \uC774 \uD30C\uC77C\uC740 \uC790\uB3D9 \uC0DD\uC131\uB429\uB2C8\uB2E4. \uD544\uC694\uC5D0 \uB530\uB77C \uC218\uC815\uD558\uC5EC \uC0AC\uC6A9\uD558\uC138\uC694.
*/
${s?`import type { ExtractSchema } from './${i.default.relative(i.default.dirname(t),s).replace(/\.ts$/,"").replace(/\\/g,"/")}';`:""}
/**
* \uC77C\uBC18\uC801\uC73C\uB85C \uC0AC\uC6A9\uB418\uB294 \uD0C0\uC785 \uBCC4\uCE6D\uB4E4
* \uD544\uC694\uC5D0 \uB530\uB77C \uCD94\uAC00\uD558\uAC70\uB098 \uC218\uC815\uD558\uC138\uC694
*/
${e.map(n=>{let r=M(n);return r?`export type ${r} = ExtractSchema<'${n}'>;`:`// export type ${n} = ExtractSchema<'${n}'>;`}).join(`
`)}
/**
* \uBAA8\uB4E0 \uC2A4\uD0A4\uB9C8 \uD0C0\uC785\uB4E4 (\uCC38\uACE0\uC6A9)
* \uC544\uB798 \uD0C0\uC785\uB4E4\uC744 \uC704\uC640 \uAC19\uC774 \uBCC4\uCE6D\uC73C\uB85C \uB9CC\uB4E4\uC5B4 \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4:
*
${e.map(n=>` * - ${n}`).join(`
`)}
*/
`;await p.default.mkdir(i.default.dirname(t),{recursive:!0}),await p.default.writeFile(t,o)}function M(t){return{User:"User",Error:"ErrorResponse",CreateUserRequest:"CreateUserRequest",UpdateUserRequest:"UpdateUserRequest",PaginatedUserResponse:"PaginatedResponse",ApiError:"ApiError",ValidationError:"ValidationError"}[t]||null}var P="0.1.0";m.program.name("ts5deco-express-controller").description("TypeScript 5 Modern Decorator Express Controller Framework CLI").version(P);m.program.command("generate").description("Generate TypeScript types from OpenAPI specification").option("-i, --input <path>","OpenAPI specification file path","./api/openapi.yaml").option("-o, --output <path>","Output directory for generated types","./src/types/generated").option("--api-types <path>","Output path for API type aliases","./src/types/api.ts").option("--utils <path>","Output path for utility types","./src/types/openapi-utils.ts").option("--no-aliases","Skip generating type aliases").option("--no-utils","Skip generating utility types").action(async t=>{try{console.log(`\u{1F680} Generating TypeScript types from OpenAPI specification...
`),await x({input:t.input,outputDir:t.output,apiTypesPath:t.apiTypes,utilsPath:t.utils,generateAliases:t.aliases,generateUtils:t.utils}),console.log("\u2728 Type generation completed successfully!")}catch(e){console.error("\u274C Error generating types:",e instanceof Error?e.message:String(e)),process.exit(1)}});m.program.command("init").description("Initialize a new project with example OpenAPI specification").option("-d, --dir <path>","Target directory",".").action(async t=>{try{console.log(`\u{1F527} Initializing project with OpenAPI configuration...
`);let{initProject:e}=await Promise.resolve().then(()=>(w(),T));await e(t.dir),console.log("\u2728 Project initialized successfully!"),console.log(`
Next steps:`),console.log("1. Edit api/openapi.yaml to match your API"),console.log("2. Run: npx ts5deco-express-controller generate"),console.log("3. Import types in your controllers")}catch(e){console.error("\u274C Error initializing project:",e instanceof Error?e.message:String(e)),process.exit(1)}});m.program.parse();
//# sourceMappingURL=index.js.map