From cee00df3ec33592ef536d127d0f567241afd83fb Mon Sep 17 00:00:00 2001 From: Bek Masharipov Date: Sun, 2 Nov 2025 11:27:21 +0100 Subject: [PATCH 1/8] =?UTF-8?q?=F0=9F=90=9B=20Add=20missing=20support=20fo?= =?UTF-8?q?r=20PUT=20method=20in=20openApiRouter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- starter/api/rest/src/view/rest/util/openapi.util.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/starter/api/rest/src/view/rest/util/openapi.util.ts b/starter/api/rest/src/view/rest/util/openapi.util.ts index f8efc7e..49fe46f 100644 --- a/starter/api/rest/src/view/rest/util/openapi.util.ts +++ b/starter/api/rest/src/view/rest/util/openapi.util.ts @@ -245,6 +245,9 @@ export const openApiRouter = ( case 'post': router.post(route, handler) break + case 'PUT': + router.put(route, handler) + break case 'patch': router.patch(route, handler) break From 1c7cf48b8976dfbb162550450d80fc9b23a45772 Mon Sep 17 00:00:00 2001 From: Bek Masharipov Date: Sun, 2 Nov 2025 11:28:05 +0100 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=90=9B=20Remove=20hardcoded=20version?= =?UTF-8?q?=20number=20in=20CLI=20help?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Bootstrap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bootstrap.ts b/src/Bootstrap.ts index d4ea29c..469c598 100644 --- a/src/Bootstrap.ts +++ b/src/Bootstrap.ts @@ -87,7 +87,7 @@ export class Bootstrap { } cli = cli - .version('1.0.0') + .version() .help() .check(argv => { for (const [key, val] of Object.entries(argv)) { From 4966ad6ea846ceca4c1353104a6fd1fd6af94689 Mon Sep 17 00:00:00 2001 From: Bek Masharipov Date: Sun, 2 Nov 2025 11:31:03 +0100 Subject: [PATCH 3/8] =?UTF-8?q?=F0=9F=93=9D=20Recommend=20npx=20installati?= =?UTF-8?q?on=20from=20npm=20registry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 03adf36..996383a 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,10 @@ CLI to help you set up a Node.js TypeScript project. The setup includes: ## Usage -Run directly from GitHub repo via npx: +Run directly from npm via npx: ``` -Usage: npm exec --ignore-scripts -- github:AckeeCZ/create-node-app [OPTIONS] +Usage: npx @ackee/create-node-app [OPTIONS] Options: -d, --dir Destination directory [string] [default: "./node-app"] From 5a36405e12b0d16d72916716faeabaa90b67b358 Mon Sep 17 00:00:00 2001 From: Bek Masharipov Date: Sun, 2 Nov 2025 19:50:29 +0100 Subject: [PATCH 4/8] =?UTF-8?q?=E2=9C=A8=20Infer=20HTTP=20success=20status?= =?UTF-8?q?=20codes=20from=20OpenAPI=20spec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/rest/src/view/cli/openapi/generate.ts | 7 ++++++ .../rest/src/view/rest/util/openapi.util.ts | 24 +++++++++++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/starter/api/rest/src/view/cli/openapi/generate.ts b/starter/api/rest/src/view/cli/openapi/generate.ts index d59bbaf..679ca51 100644 --- a/starter/api/rest/src/view/cli/openapi/generate.ts +++ b/starter/api/rest/src/view/cli/openapi/generate.ts @@ -33,9 +33,16 @@ export const run = async (argv: any): Promise => { const pathSpec = spec.paths[path] Object.keys(pathSpec).forEach(method => { if (pathSpec[method]?.operationId) { + const operationResponses = pathSpec[method].responses ?? {} + const successStatus = + Object.keys(operationResponses).find(status => + status.startsWith('2') + ) ?? '200' + acc[pathSpec[method].operationId] = { method, path, + successStatus: parseInt(successStatus, 10), } } }) diff --git a/starter/api/rest/src/view/rest/util/openapi.util.ts b/starter/api/rest/src/view/rest/util/openapi.util.ts index 49fe46f..fce2e33 100644 --- a/starter/api/rest/src/view/rest/util/openapi.util.ts +++ b/starter/api/rest/src/view/rest/util/openapi.util.ts @@ -126,11 +126,23 @@ export type ApiHandler = ( ) => TRes | Promise const asyncHandler = - (fn: ApiHandler): express.Handler => + (fn: ApiHandler, operationId?: OperationIds): express.Handler => async (req, res, next) => { try { const result = await fn(req.context, req, res) - res.json(result) + + if (operationId && !res.headersSent) { + const metadata = operationPaths[operationId] + if (metadata?.successStatus) { + res.status(metadata.successStatus) + } + } + + if (result !== undefined) { + res.json(result) + } else { + res.end() + } } catch (error: unknown) { next(error) } @@ -200,8 +212,9 @@ export type RestApiController< } const handleOperationAsync = ( - fn: OperationHandler -): OpenApiHandler => asyncHandler(fn as any) as any + fn: OperationHandler, + operationId: OperationId +): OpenApiHandler => asyncHandler(fn as any, operationId) as any const createRestController = ( def: RouteHandlers @@ -211,7 +224,8 @@ const createRestController = ( return ctrl } ctrl[operationId as SubsetOperationIds] = handleOperationAsync( - handler as any + handler as any, + operationId ) as any return ctrl }, {} as RestApiController) From 73b8e91f6f9e5dc2fd01ada8287b14d79d8df1a7 Mon Sep 17 00:00:00 2001 From: Bek Masharipov Date: Sun, 2 Nov 2025 19:53:50 +0100 Subject: [PATCH 5/8] =?UTF-8?q?=F0=9F=90=9B=20Fix=20return=20type=20to=20v?= =?UTF-8?q?oid=20for=20"No=20content"=20http=20responses?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- starter/api/rest/src/view/rest/util/openapi.util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/starter/api/rest/src/view/rest/util/openapi.util.ts b/starter/api/rest/src/view/rest/util/openapi.util.ts index fce2e33..4a77caf 100644 --- a/starter/api/rest/src/view/rest/util/openapi.util.ts +++ b/starter/api/rest/src/view/rest/util/openapi.util.ts @@ -170,7 +170,7 @@ type MimeContentValue< type OpenApiContentTypes> = { [K in keyof OpenApiContent]: OpenApiContent[K] extends Content ? MimeContentValue - : never + : void }[keyof OpenApiContent] export type OperationParams = From 544dbfd2c285bcd334f59480425ca1c7325d9900 Mon Sep 17 00:00:00 2001 From: Bek Masharipov Date: Mon, 1 Dec 2025 23:07:34 +0100 Subject: [PATCH 6/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20result=20ch?= =?UTF-8?q?eck=20and=20type=20assertion=20in=20async=20handler=20and=20con?= =?UTF-8?q?troller?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- starter/api/rest/src/view/rest/util/openapi.util.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/starter/api/rest/src/view/rest/util/openapi.util.ts b/starter/api/rest/src/view/rest/util/openapi.util.ts index 4a77caf..40b40dd 100644 --- a/starter/api/rest/src/view/rest/util/openapi.util.ts +++ b/starter/api/rest/src/view/rest/util/openapi.util.ts @@ -138,7 +138,7 @@ const asyncHandler = } } - if (result !== undefined) { + if (result != undefined) { res.json(result) } else { res.end() @@ -225,7 +225,7 @@ const createRestController = ( } ctrl[operationId as SubsetOperationIds] = handleOperationAsync( handler as any, - operationId + operationId as SubsetOperationIds ) as any return ctrl }, {} as RestApiController) From 5ffc1f28419c43ffbc501cf39abd7a0e77b5a20c Mon Sep 17 00:00:00 2001 From: Bek Masharipov Date: Thu, 1 Jan 2026 23:49:53 +0100 Subject: [PATCH 7/8] =?UTF-8?q?=E2=9C=A8=20Include=203xx=20status=20codes?= =?UTF-8?q?=20when=20searching=20for=20a=20default=20response=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- starter/api/rest/src/view/cli/openapi/generate.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/starter/api/rest/src/view/cli/openapi/generate.ts b/starter/api/rest/src/view/cli/openapi/generate.ts index 679ca51..d67fbb0 100644 --- a/starter/api/rest/src/view/cli/openapi/generate.ts +++ b/starter/api/rest/src/view/cli/openapi/generate.ts @@ -34,10 +34,10 @@ export const run = async (argv: any): Promise => { Object.keys(pathSpec).forEach(method => { if (pathSpec[method]?.operationId) { const operationResponses = pathSpec[method].responses ?? {} - const successStatus = - Object.keys(operationResponses).find(status => - status.startsWith('2') - ) ?? '200' + const responseCodes = Object.keys(operationResponses) + const successStatus = responseCodes.find(s => s.startsWith('2')) + ?? responseCodes.find(s => s.startsWith('3')) + ?? '200'; acc[pathSpec[method].operationId] = { method, From d108b2b85ac4bc5e51c85ff0ef26a7837e5108f4 Mon Sep 17 00:00:00 2001 From: Bek Masharipov Date: Thu, 1 Jan 2026 23:52:24 +0100 Subject: [PATCH 8/8] =?UTF-8?q?=F0=9F=90=9B=20Set=20a=20default=20status?= =?UTF-8?q?=20code=20before=20executing=20endpoint=20handler=20to=20preven?= =?UTF-8?q?t=20the=20overwrite=20of=20explicit=20status=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/rest/src/view/rest/util/openapi.util.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/starter/api/rest/src/view/rest/util/openapi.util.ts b/starter/api/rest/src/view/rest/util/openapi.util.ts index 40b40dd..d45b93b 100644 --- a/starter/api/rest/src/view/rest/util/openapi.util.ts +++ b/starter/api/rest/src/view/rest/util/openapi.util.ts @@ -129,19 +129,21 @@ const asyncHandler = (fn: ApiHandler, operationId?: OperationIds): express.Handler => async (req, res, next) => { try { - const result = await fn(req.context, req, res) - - if (operationId && !res.headersSent) { + if (operationId) { const metadata = operationPaths[operationId] if (metadata?.successStatus) { res.status(metadata.successStatus) } } - if (result != undefined) { - res.json(result) - } else { - res.end() + const result = await fn(req.context, req, res) + + if (!res.headersSent) { + if (result != undefined) { + res.json(result) + } else { + res.end() + } } } catch (error: unknown) { next(error)