From 159254cea4414acdd7da3d56cedafe01573687ba Mon Sep 17 00:00:00 2001 From: Jeongho Nam Date: Fri, 17 Feb 2023 03:33:57 +0900 Subject: [PATCH 1/2] Complement #248 - recommend `ts-patch` --- README.md | 31 +-- package.json | 6 +- packages/core/README.md | 24 +- packages/core/package.json | 10 +- .../core/src/decorators/EncryptedRoute.ts | 4 +- packages/core/src/decorators/TypedRoute.ts | 5 +- packages/core/src/executable/core.ts | 57 +++-- .../src/executable/internal/ArgumentParser.ts | 144 +++++++++++ .../executable/internal/CommandExecutor.ts | 8 + .../src/executable/internal/CommandParser.ts | 15 -- .../executable/internal/CoreSetupWizard.ts | 225 ------------------ .../src/executable/internal/PackageManager.ts | 99 ++++++++ .../executable/internal/PluginConfigurator.ts | 128 ++++++++++ .../ConsumerSaleArticleCommentsController.ts | 2 +- .../test/controllers/FilesystemController.ts | 6 +- .../controllers/SaleInquiriesController.ts | 2 +- .../core/test/controllers/SystemController.ts | 2 +- packages/core/test/tsconfig.json | 5 +- src/CommandParser.ts | 15 -- src/NestiaSetupWizard.ts | 99 ++++---- src/index.ts | 23 +- src/internal/ArgumentParser.ts | 144 +++++++++++ src/internal/CommandExecutor.ts | 8 + src/internal/PackageManager.ts | 99 ++++++++ src/internal/PluginConfigurator.ts | 128 ++++++++++ 25 files changed, 897 insertions(+), 392 deletions(-) create mode 100644 packages/core/src/executable/internal/ArgumentParser.ts create mode 100644 packages/core/src/executable/internal/CommandExecutor.ts delete mode 100644 packages/core/src/executable/internal/CommandParser.ts delete mode 100644 packages/core/src/executable/internal/CoreSetupWizard.ts create mode 100644 packages/core/src/executable/internal/PackageManager.ts create mode 100644 packages/core/src/executable/internal/PluginConfigurator.ts delete mode 100644 src/CommandParser.ts create mode 100644 src/internal/ArgumentParser.ts create mode 100644 src/internal/CommandExecutor.ts create mode 100644 src/internal/PackageManager.ts create mode 100644 src/internal/PluginConfigurator.ts diff --git a/README.md b/README.md index bb58676d4..1c48b78ee 100644 --- a/README.md +++ b/README.md @@ -30,29 +30,30 @@ Just run above command, then boilerplate project would be constructed. npx nestia setup ``` -When you want to use `nestia` in orindary project, just type above command. +Just type `npx nestia setup`, that's all. -All installation and configuration processes would be automatically done. - -Also, you can specify package manager or target `tsconfig.json` file like below: +If you've installed [ttypescript](https://github.com/cevek/ttypescript) during setup, you should compile `@nestia/core` utilization code through `ttsc` command, instead of `tsc`. ```bash -npx nestia setup --manager npm -npx nestia setup --manager pnpm -npx nestia setup --manager yarn +# COMPILE THROUGH TTYPESCRIPT +npx ttsc -npx nestia setup --project tsconfig.json -npx nestia setup --project tsconfig.test.json +# RUN TS-NODE WITH TTYPESCRIPT +npx ts-node -C ttypescript src/index.ts ``` -After the setup, you can compile `@nestia/core` utilization code by using `ttsc` ([`ttypescript`](https://github.com/cevek/ttypescript)) command. If you want to run your TypeScript file directly through `ts-node`, add `-C ttypescript` argument like below: +Otherwise, you've chosen [ts-patch](https://github.com/nonara/ts-patch), you can use original `tsc` command. However, [ts-patch](https://github.com/nonara/ts-patch) hacks `node_modules/typescript` source code. Also, whenever update `typescript` version, you've to run `npm run prepare` command repeatedly. + +By the way, when using `@nest/cli`, you must just choose [ts-patch](https://github.com/nonara/ts-patch). ```bash -# COMPILE THROUGH TTYPESCRIPT -npx ttsc +# USE ORIGINAL TSC COMMAND +tsc +npx ts-node src/index.ts -# RUN TS-NODE WITH TTYPESCRIPT -npx ts-node -C ttypescript src/index.ts +# WHENVER UPDATE +npm install --save-dev typescript@latest +npm run prepare ``` ### Manual Setup @@ -89,7 +90,7 @@ export class BbsArticlesController { * @param inupt Content to store * @returns Newly archived article */ - @TypedRoute.Post() // 10x faster and safer JSON.stringify() + @TypedRoute.Post() // 50x faster and safer JSON.stringify() public async store( @TypedBody() input: IBbsArticle.IStore // super-fast validator ): Promise; diff --git a/package.json b/package.json index d18e60f52..e88b3cf61 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nestia", - "version": "4.0.5", + "version": "4.0.6", "description": "Nestia CLI", "main": "bin/index.js", "bin": { @@ -31,7 +31,11 @@ "devDependencies": { "@nestia/core": "^1.0.6", "@nestia/sdk": "^1.0.1", + "@types/inquirer": "^9.0.3", "@types/node": "^18.11.16", + "commander": "^10.0.0", + "comment-json": "^4.2.3", + "inquirer": "^8.2.5", "rimraf": "^3.0.2", "typescript": "^4.9.4" }, diff --git a/packages/core/README.md b/packages/core/README.md index 4c8815ebe..720cebbc4 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -58,24 +58,30 @@ npx nestia setup npx @nestia/core setup ``` -When you run `npx nestia setup` command, all installation and configuration processes would be automatically done. If you want to setup `@nestia/core` only, run `npx @nestia/core setup` command instead. +Just type `npx nestia setup`, that's all. -After the setup has been fully completed, you can compile your backend server code by using `ttsc` command. If you want to run your TypeScript file directly through `ts-node`, add `-C ttypescript` argument like below: +If you've installed [ttypescript](https://github.com/cevek/ttypescript) during setup, you should compile `@nestia/core` utilization code through `ttsc` command, instead of `tsc`. ```bash +# COMPILE THROUGH TTYPESCRIPT npx ttsc + +# RUN TS-NODE WITH TTYPESCRIPT npx ts-node -C ttypescript src/index.ts ``` -Also, you can specify package manager or target `tsconfig.json` file like below: +Otherwise, you've chosen [ts-patch](https://github.com/nonara/ts-patch), you can use original `tsc` command. However, [ts-patch](https://github.com/nonara/ts-patch) hacks `node_modules/typescript` source code. Also, whenever update `typescript` version, you've to run `npm run prepare` command repeatedly. + +By the way, when using `@nest/cli`, you must just choose [ts-patch](https://github.com/nonara/ts-patch). ```bash -npx @nestia/core setup --manager npm -npx @nestia/core setup --manager pnpm -npx @nestia/core setup --manager yarn +# USE ORIGINAL TSC COMMAND +tsc +npx ts-node src/index.ts -npx @nestia/core setup --project tsconfig.json -npx @nestia/core setup --project tsconfig.test.json +# HOWEVER, WHENVER UPDATE +npm install --save-dev typescript@latest +npm run prepare ``` ### Manual Setup @@ -99,7 +105,7 @@ export class BbsArticlesController { * @param inupt Content to store * @returns Newly archived article */ - @TypedRoute.Put(":id") // 10x faster and safer JSON.stringify() + @TypedRoute.Put(":id") // 50x faster and safer JSON.stringify() public async store( @TypedParam("section", "string") section: string, @TypedParam("id", "uuid") id: string, diff --git a/packages/core/package.json b/packages/core/package.json index 029f9b232..c46393f93 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@nestia/core", - "version": "1.0.10", + "version": "1.0.11", "description": "Super-fast validation decorators of NestJS", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -39,16 +39,20 @@ "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.0.0", "@types/express": "^4.17.15", + "@types/inquirer": "^9.0.3", "@typescript-eslint/eslint-plugin": "^5.46.1", "@typescript-eslint/parser": "^5.46.1", + "commander": "^10.0.0", "comment-json": "^4.2.3", "eslint-plugin-deprecation": "^1.3.3", "git-last-commit": "^1.0.1", + "inquirer": "^8.2.5", "rimraf": "^3.0.2", "ts-node": "^10.9.1", "tstl": "^2.5.13", "ttypescript": "^1.5.15", - "typescript": "^4.9.5" + "typescript": "^4.9.5", + "typescript-transform-paths": "^3.4.6" }, "dependencies": { "@nestia/fetcher": "^1.0.0", @@ -59,7 +63,7 @@ "raw-body": "*", "reflect-metadata": "*", "rxjs": "*", - "typia": "^3.4.22" + "typia": "^3.5.3" }, "peerDependencies": { "ttypescript": ">= 1.5.15", diff --git a/packages/core/src/decorators/EncryptedRoute.ts b/packages/core/src/decorators/EncryptedRoute.ts index f54af47a1..ab3d02c0f 100644 --- a/packages/core/src/decorators/EncryptedRoute.ts +++ b/packages/core/src/decorators/EncryptedRoute.ts @@ -35,8 +35,8 @@ import { route_error } from "./internal/route_error"; * * `EncryptedRoute` is a module containing router decorator functions which encrypts * response body data through AES-128/256 encryption. Furthermore, they can boost - * up JSON string conversion speed about 10x times faster, even type safe through - * [typia](https://github.com/samchon/typia). + * up JSON string conversion speed about 50x times faster than `class-transformer`, + * even type safe through [typia](https://github.com/samchon/typia). * * For reference, router functions of `EncryptedRoute` can convert custom error classes * to regular {@link nest.HttpException} class automatically, through diff --git a/packages/core/src/decorators/TypedRoute.ts b/packages/core/src/decorators/TypedRoute.ts index 4c6b5fe83..e48bc7f07 100644 --- a/packages/core/src/decorators/TypedRoute.ts +++ b/packages/core/src/decorators/TypedRoute.ts @@ -29,8 +29,9 @@ import { route_error } from "./internal/route_error"; * Type safe router decorator functions. * * `TypedRoute` is a module containing router decorator functions which can boost up - * JSON string conversion speed about 10x times faster. Furthermore, such JSON string - * conversion is even type safe through [typia](https://github.com/samchon/typia). + * JSON string conversion speed about 50x times faster than `class-transformer`. + * Furthermore, such JSON string conversion is even type safe through + * [typia](https://github.com/samchon/typia). * * For reference, router functions of `TypedRoute` can convert custom error classes to * the regular {@link nest.HttpException} class automatically, through diff --git a/packages/core/src/executable/core.ts b/packages/core/src/executable/core.ts index 6ca568aa6..859a095fd 100644 --- a/packages/core/src/executable/core.ts +++ b/packages/core/src/executable/core.ts @@ -1,6 +1,8 @@ #!/usr/bin/env node -import { CommandParser } from "./internal/CommandParser"; -import { CoreSetupWizard } from "./internal/CoreSetupWizard"; +import { ArgumentParser } from "./internal/ArgumentParser"; +import { CommandExecutor } from "./internal/CommandExecutor"; +import { PackageManager } from "./internal/PackageManager"; +import { PluginConfigurator } from "./internal/PluginConfigurator"; const USAGE = `Wrong command has been detected. Use like below: @@ -20,22 +22,41 @@ function halt(desc: string): never { } async function setup(): Promise { - const options: Record = CommandParser.parse( - process.argv.slice(3), - ); - const manager: string = options.manager ?? "npm"; - const compiler: string = options.compiler ?? "ttypescript"; - const project: string = options.project ?? "tsconfig.json"; - console.log(options); - - if ( - (compiler !== "ttypescript" && compiler !== "ts-patch") || - (manager !== "npm" && manager !== "pnpm" && manager !== "yarn") - ) - halt(USAGE); - else if (compiler === "ttypescript") - await CoreSetupWizard.ttypescript({ manager, project }); - else await CoreSetupWizard.tsPatch({ manager, project }); + console.log("----------------------------------------"); + console.log(" Nestia Setup Wizard"); + console.log("----------------------------------------"); + + // LOAD PACKAGE.JSON INFO + const pack: PackageManager = await PackageManager.mount(); + + // TAKE ARGUMENTS + const args: ArgumentParser.IArguments = await ArgumentParser.parse(pack); + + // INSTALL TYPESCRIPT + pack.install({ dev: true, modulo: "typescript" }); + args.project ??= (() => { + CommandExecutor.run("npx tsc --init", false); + return (args.project = "tsconfig.json"); + })(); + pack.install({ dev: true, modulo: "ts-node" }); + + // INSTALL COMPILER + pack.install({ dev: true, modulo: args.compiler }); + if (args.compiler === "ts-patch") { + await pack.save((data) => { + data.scripts ??= {}; + if (typeof data.scripts.prepare === "string") + data.scripts.prepare = + "ts-patch install && " + data.scripts.prepare; + else data.scripts.prepare = "ts-patch install"; + }); + CommandExecutor.run("npm run prepare", false); + } + + // INSTALL AND CONFIGURE TYPIA + pack.install({ dev: false, modulo: "typia" }); + pack.install({ dev: false, modulo: "@nestia/core" }); + await PluginConfigurator.configure(pack, args); } async function main(): Promise { diff --git a/packages/core/src/executable/internal/ArgumentParser.ts b/packages/core/src/executable/internal/ArgumentParser.ts new file mode 100644 index 000000000..cc8fd9728 --- /dev/null +++ b/packages/core/src/executable/internal/ArgumentParser.ts @@ -0,0 +1,144 @@ +import type CommanderModule from "commander"; +import fs from "fs"; +import type * as InquirerModule from "inquirer"; +import path from "path"; + +import { PackageManager } from "./PackageManager"; + +export namespace ArgumentParser { + export interface IArguments { + compiler: "ts-patch" | "ttypescript"; + manager: "npm" | "pnpm" | "yarn"; + project: string | null; + } + + export async function parse(pack: PackageManager): Promise { + // INSTALL TEMPORARY PACKAGES + const newbie = { + commander: pack.install({ + dev: true, + modulo: "commander", + version: "10.0.0", + silent: true, + }), + inquirer: pack.install({ + dev: true, + modulo: "inquirer", + version: "8.2.5", + silent: true, + }), + }; + + // TAKE OPTIONS + const output: IArguments | Error = await (async () => { + try { + return await _Parse(pack); + } catch (error) { + return error as Error; + } + })(); + + // REMOVE TEMPORARY PACKAGES + if (newbie.commander) pack.erase({ modulo: "commander", silent: true }); + if (newbie.inquirer) pack.erase({ modulo: "inquirer", silent: true }); + + // RETURNS + if (output instanceof Error) throw output; + return output; + } + + async function _Parse(pack: PackageManager): Promise { + // PREPARE ASSETS + const { createPromptModule }: typeof InquirerModule = await import( + path.join(pack.directory, "node_modules", "inquirer") + ); + const { program }: typeof CommanderModule = await import( + path.join(pack.directory, "node_modules", "commander") + ); + + program.option("--compiler [compiler]", "compiler type"); + program.option("--manager [manager", "package manager"); + program.option("--project [project]", "tsconfig.json file location"); + + // INTERNAL PROCEDURES + const questioned = { value: false }; + const action = ( + closure: (options: Partial) => Promise, + ) => { + return new Promise((resolve, reject) => { + program.action(async (options) => { + try { + resolve(await closure(options)); + } catch (exp) { + reject(exp); + } + }); + program.parseAsync().catch(reject); + }); + }; + const select = + (name: string) => + (message: string) => + async ( + choices: Choice[], + ): Promise => { + questioned.value = true; + return ( + await createPromptModule()({ + type: "list", + name: name, + message: message, + choices: choices, + }) + )[name]; + }; + const configure = async () => { + const fileList: string[] = await ( + await fs.promises.readdir(process.cwd()) + ).filter( + (str) => + str.substring(0, 8) === "tsconfig" && + str.substring(str.length - 5) === ".json", + ); + if (fileList.length === 0) { + if (process.cwd() !== pack.directory) + throw new Error(`Unable to find "tsconfig.json" file.`); + return null; + } else if (fileList.length === 1) return fileList[0]; + return select("tsconfig")("TS Config File")(fileList); + }; + + // DO CONSTRUCT + return action(async (options) => { + if (options.compiler === undefined) { + console.log(COMPILER_DESCRIPTION); + options.compiler = await select("compiler")(`Compiler`)( + pack.data.scripts?.build === "nest build" + ? ["ts-patch" as const, "ttypescript" as const] + : ["ttypescript" as const, "ts-patch" as const], + ); + } + options.manager ??= await select("manager")("Package Manager")([ + "npm" as const, + "pnpm" as const, + "yarn" as const, + ]); + pack.manager = options.manager; + options.project ??= await configure(); + + if (questioned.value) console.log(""); + return options as IArguments; + }); + } +} + +const COMPILER_DESCRIPTION = [ + `About compiler, if you adapt "ttypescript", you should use "ttsc" instead.`, + ``, + `Otherwise, you choose "ts-patch", you can use the original "tsc" command.`, + `However, the "ts-patch" hacks "node_modules/typescript" source code.`, + `Also, whenever update "typescript", you've to run "npm run prepare" command.`, + ``, + `By the way, when using "@nest/cli", you must just choose "ts-patch".`, + ``, +].join("\n"); diff --git a/packages/core/src/executable/internal/CommandExecutor.ts b/packages/core/src/executable/internal/CommandExecutor.ts new file mode 100644 index 000000000..e3e654a00 --- /dev/null +++ b/packages/core/src/executable/internal/CommandExecutor.ts @@ -0,0 +1,8 @@ +import cp from "child_process"; + +export namespace CommandExecutor { + export function run(str: string, silent: boolean): void { + if (silent === false) console.log(str); + cp.execSync(str, { stdio: "ignore" }); + } +} diff --git a/packages/core/src/executable/internal/CommandParser.ts b/packages/core/src/executable/internal/CommandParser.ts deleted file mode 100644 index 9dcc60c29..000000000 --- a/packages/core/src/executable/internal/CommandParser.ts +++ /dev/null @@ -1,15 +0,0 @@ -export namespace CommandParser { - export function parse(argList: string[]): Record { - const output: Record = {}; - argList.forEach((arg, i) => { - if (arg.startsWith("--") === false) return; - - const key = arg.slice(2); - const value: string | undefined = argList[i + 1]; - if (value === undefined || value.startsWith("--")) return; - - output[key] = value; - }); - return output; - } -} diff --git a/packages/core/src/executable/internal/CoreSetupWizard.ts b/packages/core/src/executable/internal/CoreSetupWizard.ts deleted file mode 100644 index d9facca70..000000000 --- a/packages/core/src/executable/internal/CoreSetupWizard.ts +++ /dev/null @@ -1,225 +0,0 @@ -import cp from "child_process"; -import type Comment from "comment-json"; -import fs from "fs"; - -export namespace CoreSetupWizard { - export interface IArguments { - manager: "npm" | "pnpm" | "yarn"; - project: string; - } - - export async function ttypescript( - args: string | IArguments, - ): Promise { - // POLYFILL - if (typeof args === "string") - args = { - manager: args as "npm", - project: "tsconfig.json", - }; - - // INSTALL - const pack: any = await prepare(args.manager); - add(args.manager)(pack)("ttypescript", true); - add(args.manager)(pack)("ts-node", true); - - // TSCONFIG.JSON - await configure(args)(pack); - } - - export async function tsPatch(args: string | IArguments): Promise { - // POLYFILL - if (typeof args === "string") - args = { - manager: args as "npm", - project: "tsconfig.json", - }; - - // INSTALL - add(args.manager)(await prepare(args.manager))("ts-patch", true); - execute("npx ts-patch install"); - - // PACKAGE.JSON - const pack: any = JSON.parse( - await fs.promises.readFile("package.json", "utf8"), - ); - if (!pack.scripts || typeof pack.scripts !== "object") - pack.scripts = {}; - if (typeof pack.scripts.prepare === "string") { - if (pack.scripts.prepare.indexOf("ts-patch install") === -1) - pack.scripts.prepare = - "ts-patch install && " + pack.scripts.prepare; - } else pack.scripts.prepare = "ts-patch install"; - - await fs.promises.writeFile( - "package.json", - JSON.stringify(pack, null, 2), - "utf8", - ); - - // TSCONFIG.JSON - await configure(args)(pack); - } - - async function prepare(manager: string): Promise { - if (fs.existsSync("package.json") === false) - halt(() => {})("make package.json file or move to it."); - - const pack: any = JSON.parse( - await fs.promises.readFile("package.json", "utf8"), - ); - add(manager)(pack)("typescript", true); - add(manager)(pack)("typia", false); - add(manager)(pack)("@nestia/core", false); - return pack; - } - - const configure = - (args: IArguments) => - async (pack: any): Promise => { - if (fs.existsSync(args.project) === false) { - if (args.project === "tsconfig.json") execute("npx tsc --init"); - if (fs.existsSync(args.project) === false) - halt(() => {})(`${args.project} file does not exist.`); - } - - const temporary: boolean = !fs.existsSync( - "node_modules/comment-json", - ); - if (temporary === true) - add(args.manager)(pack)("comment-json", true); - - const halter: (msg: string) => never = halt(() => { - if (temporary === true) - remove(args.manager)("comment-json", true); - }); - - // READ TSCONFIG FILE - const Comment: typeof import("comment-json") = await import( - process.cwd() + "/node_modules/comment-json" - ); - const config: Comment.CommentObject = Comment.parse( - await fs.promises.readFile(args.project, "utf8"), - ) as Comment.CommentObject; - const options = config.compilerOptions as - | Comment.CommentObject - | undefined; - if (options === undefined) - halter( - `${args.project} file does not have "compilerOptions" property.`, - ); - - const plugins: Comment.CommentArray = - (() => { - const plugins = options.plugins as - | Comment.CommentArray - | undefined; - if (plugins === undefined) - return (options.plugins = [] as any); - else if (!Array.isArray(plugins)) - halter( - `"plugins" property of ${args.project} must be array type.`, - ); - return plugins; - })(); - - // CHECK WHETHER CONFIGURED - const strict: boolean = options.strict === true; - const core: Comment.CommentObject | undefined = plugins.find( - (p) => - typeof p === "object" && - p !== null && - p.transform === "@nestia/core/lib/transform", - ); - const typia: Comment.CommentObject | undefined = plugins.find( - (p) => - typeof p === "object" && - p !== null && - p.transform === "typia/lib/transform", - ); - - if (strict === true && core !== undefined && typia !== undefined) { - console.log( - `you've been already configured the ${args.project} file.`, - ); - } else { - // DO CONFIGURE - options.strict = true; - if (core === undefined) - plugins.push( - Comment.parse(`{ - "transform": "@nestia/core/lib/transform", - /** - * Validate request body. - * - * - "assert": Use typia.assert() function - * - "is": Use typia.is() function - * - "validate": Use typia.validate() function - */ - "validate": "assert", - - /** - * Validate JSON typed response body. - * - * - null: Just use JSON.stringify() function, without boosting - * - "stringify": Use typia.stringify() function, but dangerous - * - "assert": Use typia.assertStringify() function - * - "is": Use typia.isStringify() function - * - "validate": Use typia.validateStringify() function - */ - "stringify": "is" - }`) as Comment.CommentObject, - ); - if (typia === undefined) - plugins.push( - Comment.parse(`{ - "transform": "typia/lib/transform" - }`) as Comment.CommentObject, - ); - await fs.promises.writeFile( - args.project, - Comment.stringify(config, null, 2), - ); - } - if (temporary === true) remove(args.manager)("comment-json", false); - }; -} - -const add = - (manager: string) => - (pack: any) => - (modulo: string, devOnly: boolean): void => { - const exists: boolean = - (devOnly === false - ? !!pack.dependencies && !!pack.dependencies[modulo] - : !!pack.devDependencies && !!pack.devDependencies[modulo]) && - fs.existsSync("node_modules/" + modulo); - const middle: string = - manager === "yarn" - ? `add${devOnly ? " -D" : ""}` - : `install ${devOnly ? "--save-dev" : "--save"}`; - if (exists === false) execute(`${manager} ${middle} ${modulo}`); - }; - -const remove = - (manager: string) => - (modulo: string, devOnly: boolean): void => { - const middle: string = - manager === "yarn" - ? `remove${devOnly ? " -D" : ""}` - : `uninstall ${devOnly ? "--save-dev" : "--save"}`; - execute(`${manager} ${middle} ${modulo}`); - }; - -const halt = - (closer: () => any) => - (desc: string): never => { - closer(); - console.error(desc); - process.exit(-1); - }; - -function execute(command: string): void { - console.log(command); - cp.execSync(command, { stdio: "inherit" }); -} diff --git a/packages/core/src/executable/internal/PackageManager.ts b/packages/core/src/executable/internal/PackageManager.ts new file mode 100644 index 000000000..b2f3174a5 --- /dev/null +++ b/packages/core/src/executable/internal/PackageManager.ts @@ -0,0 +1,99 @@ +import fs from "fs"; +import path from "path"; + +import { CommandExecutor } from "./CommandExecutor"; + +export class PackageManager { + public manager: string = "npm"; + public get file(): string { + return path.join(this.directory, "package.json"); + } + + public static async mount(): Promise { + const location: string | null = await find(process.cwd()); + if (location === null) + throw new Error(`Unable to find "package.json" file`); + + return new PackageManager( + location, + await this.load(path.join(location, "package.json")), + ); + } + + public async save(modifier: (data: Package.Data) => void): Promise { + const content: string = await fs.promises.readFile(this.file, "utf8"); + this.data = JSON.parse(content); + modifier(this.data); + + return fs.promises.writeFile( + this.file, + JSON.stringify(this.data, null, 2), + "utf8", + ); + } + + public install(props: { + dev: boolean; + silent?: boolean; + modulo: string; + version?: string; + }): boolean { + const container = props.dev + ? this.data.devDependencies + : this.data.dependencies; + if ( + !!container?.[props.modulo] && + fs.existsSync( + path.join(this.directory, "node_modules", props.modulo), + ) + ) + return false; + + const middle: string = + this.manager === "yarn" + ? `add${props.dev ? " -D" : ""}` + : `install ${props.dev ? "--save-dev" : "--save"}`; + CommandExecutor.run( + `${this.manager} ${middle} ${props.modulo}${ + props.version ? `@${props.version}` : "" + }`, + !!props.silent, + ); + return true; + } + + public erase(props: { modulo: string; silent?: boolean }): void { + const middle: string = this.manager === "yarn" ? "remove" : "uninstall"; + CommandExecutor.run( + `${this.manager} ${middle} ${props.modulo}`, + !!props.silent, + ); + } + + private constructor( + public readonly directory: string, + public data: Package.Data, + ) {} + + private static async load(file: string): Promise { + const content: string = await fs.promises.readFile(file, "utf8"); + return JSON.parse(content); + } +} +export namespace Package { + export interface Data { + scripts?: Record; + dependencies?: Record; + devDependencies?: Record; + } +} + +async function find( + directory: string = process.cwd(), + depth: number = 0, +): Promise { + const location: string = path.join(directory, "package.json"); + if (fs.existsSync(location)) return directory; + else if (depth > 1) return null; + return find(path.join(directory, ".."), depth + 1); +} diff --git a/packages/core/src/executable/internal/PluginConfigurator.ts b/packages/core/src/executable/internal/PluginConfigurator.ts new file mode 100644 index 000000000..0ad528516 --- /dev/null +++ b/packages/core/src/executable/internal/PluginConfigurator.ts @@ -0,0 +1,128 @@ +import type Comment from "comment-json"; +import fs from "fs"; +import path from "path"; + +import { ArgumentParser } from "./ArgumentParser"; +import { PackageManager } from "./PackageManager"; + +export namespace PluginConfigurator { + export async function configure( + pack: PackageManager, + args: ArgumentParser.IArguments, + ): Promise { + // INSTALL COMMENT-JSON + const installed: boolean = pack.install({ + dev: true, + modulo: "comment-json", + version: "4.2.3", + silent: true, + }); + + // DO CONFIGURE + const error: Error | null = await (async () => { + try { + await _Configure(pack, args); + return null; + } catch (exp) { + return exp as Error; + } + })(); + + // REMOVE IT + if (installed) + pack.erase({ + modulo: "comment-json", + silent: true, + }); + if (error !== null) throw error; + } + + async function _Configure( + pack: PackageManager, + args: ArgumentParser.IArguments, + ): Promise { + // GET COMPILER-OPTIONS + const Comment: typeof import("comment-json") = await import( + path.join(pack.directory, "node_modules", "comment-json") + ); + + const config: Comment.CommentObject = Comment.parse( + await fs.promises.readFile(args.project!, "utf8"), + ) as Comment.CommentObject; + const compilerOptions = config.compilerOptions as + | Comment.CommentObject + | undefined; + if (compilerOptions === undefined) + throw new Error( + `${args.project} file does not have "compilerOptions" property.`, + ); + + // PREPARE PLUGINS + const plugins: Comment.CommentArray = (() => { + const plugins = compilerOptions.plugins as + | Comment.CommentArray + | undefined; + if (plugins === undefined) + return (compilerOptions.plugins = [] as any); + else if (!Array.isArray(plugins)) + throw new Error( + `"plugins" property of ${args.project} must be array type.`, + ); + return plugins; + })(); + + // CHECK WHETHER CONFIGURED + const strict: boolean = compilerOptions.strict === true; + const core: Comment.CommentObject | undefined = plugins.find( + (p) => + typeof p === "object" && + p !== null && + p.transform === "@nestia/core/lib/transform", + ); + const typia: Comment.CommentObject | undefined = plugins.find( + (p) => + typeof p === "object" && + p !== null && + p.transform === "typia/lib/transform", + ); + if (strict && !!core && !!typia) return; + + // DO CONFIGURE + compilerOptions.strict = true; + if (core === undefined) + plugins.push( + Comment.parse(`{ + "transform": "@nestia/core/lib/transform", + /** + * Validate request body. + * + * - "assert": Use typia.assert() function + * - "is": Use typia.is() function + * - "validate": Use typia.validate() function + */ + "validate": "assert", + + /** + * Validate JSON typed response body. + * + * - null: Just use JSON.stringify() function, without boosting + * - "stringify": Use typia.stringify() function, but dangerous + * - "assert": Use typia.assertStringify() function + * - "is": Use typia.isStringify() function + * - "validate": Use typia.validateStringify() function + */ + "stringify": "is" + }`) as Comment.CommentObject, + ); + if (typia === undefined) + plugins.push( + Comment.parse( + `{ "transform": "typia/lib/transform" }`, + ) as Comment.CommentObject, + ); + await fs.promises.writeFile( + args.project!, + Comment.stringify(config, null, 2), + ); + } +} diff --git a/packages/core/test/controllers/ConsumerSaleArticleCommentsController.ts b/packages/core/test/controllers/ConsumerSaleArticleCommentsController.ts index a78612365..16d1f4533 100644 --- a/packages/core/test/controllers/ConsumerSaleArticleCommentsController.ts +++ b/packages/core/test/controllers/ConsumerSaleArticleCommentsController.ts @@ -1,7 +1,7 @@ +import core from "@nestia/core"; import * as nest from "@nestjs/common"; import * as express from "express"; -import core from "../../src/index"; import { ISaleArticleComment } from "../api/structures/ISaleArticleComment"; @nest.Controller( diff --git a/packages/core/test/controllers/FilesystemController.ts b/packages/core/test/controllers/FilesystemController.ts index f8c7883aa..27a9859ab 100644 --- a/packages/core/test/controllers/FilesystemController.ts +++ b/packages/core/test/controllers/FilesystemController.ts @@ -1,13 +1,13 @@ +import core from "@nestia/core"; import * as nest from "@nestjs/common"; -import helper from "../../src"; import { IFilesystemBucket } from "../api/structures/IFilesystemBucket"; @nest.Controller("filesystem") export class FilesystemController { - @helper.TypedRoute.Get() + @core.TypedRoute.Get() public get( - @helper.TypedQuery() _input: IFilesystemBucket.IRequest, + @core.TypedQuery() _input: IFilesystemBucket.IRequest, ): IFilesystemBucket[] { return [ { diff --git a/packages/core/test/controllers/SaleInquiriesController.ts b/packages/core/test/controllers/SaleInquiriesController.ts index f794e16fd..eeee00d13 100644 --- a/packages/core/test/controllers/SaleInquiriesController.ts +++ b/packages/core/test/controllers/SaleInquiriesController.ts @@ -1,7 +1,7 @@ +import core from "@nestia/core"; import * as nest from "@nestjs/common"; import * as express from "express"; -import core from "../../src/index"; import { IPage } from "../api/structures/IPage"; import { ISaleInquiry } from "../api/structures/ISaleInquiry"; diff --git a/packages/core/test/controllers/SystemController.ts b/packages/core/test/controllers/SystemController.ts index de8cea53c..8f5136c6a 100644 --- a/packages/core/test/controllers/SystemController.ts +++ b/packages/core/test/controllers/SystemController.ts @@ -1,10 +1,10 @@ +import core from "@nestia/core"; import * as nest from "@nestjs/common"; import fs from "fs"; import git from "git-last-commit"; import { randint } from "tstl/algorithm/random"; import { Singleton } from "tstl/thread/Singleton"; -import core from "../../src/index"; import { ISystem } from "../api/structures/ISystem"; @nest.Controller("system") diff --git a/packages/core/test/tsconfig.json b/packages/core/test/tsconfig.json index d5cea8012..2e2dc0219 100644 --- a/packages/core/test/tsconfig.json +++ b/packages/core/test/tsconfig.json @@ -31,7 +31,10 @@ // "rootDir": "./", /* Specify the root folder within your source files. */ // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + "paths": { + "@nestia/core": ["../src"], + "@nestia/core/lib/*": ["../src/*"] + }, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ diff --git a/src/CommandParser.ts b/src/CommandParser.ts deleted file mode 100644 index 9dcc60c29..000000000 --- a/src/CommandParser.ts +++ /dev/null @@ -1,15 +0,0 @@ -export namespace CommandParser { - export function parse(argList: string[]): Record { - const output: Record = {}; - argList.forEach((arg, i) => { - if (arg.startsWith("--") === false) return; - - const key = arg.slice(2); - const value: string | undefined = argList[i + 1]; - if (value === undefined || value.startsWith("--")) return; - - output[key] = value; - }); - return output; - } -} diff --git a/src/NestiaSetupWizard.ts b/src/NestiaSetupWizard.ts index 320a9aef0..a869d083f 100644 --- a/src/NestiaSetupWizard.ts +++ b/src/NestiaSetupWizard.ts @@ -1,67 +1,48 @@ -import cp from "child_process"; -import fs from "fs"; +import { ArgumentParser } from "./internal/ArgumentParser"; +import { CommandExecutor } from "./internal/CommandExecutor"; +import { PackageManager } from "./internal/PackageManager"; +import { PluginConfigurator } from "./internal/PluginConfigurator"; export namespace NestiaSetupWizard { - export interface IArguments { - manager: "npm" | "pnpm" | "yarn"; - project: string; - } - - export async function ttypescript(args: IArguments): Promise { - const wizard = await prepare(args); - await wizard.ttypescript(args); - } - - export async function tsPatch(args: IArguments): Promise { - const wizard = await prepare(args); - await wizard.tsPatch(args); - } + export async function setup(): Promise { + console.log("----------------------------------------"); + console.log(" Nestia Setup Wizard"); + console.log("----------------------------------------"); - async function prepare(args: IArguments) { - if (fs.existsSync("package.json") === false) - halt(() => {})("make package.json file or move to it."); + // LOAD PACKAGE.JSON INFO + const pack: PackageManager = await PackageManager.mount(); - const pack: any = JSON.parse( - await fs.promises.readFile("package.json", "utf8"), + // TAKE ARGUMENTS + const args: ArgumentParser.IArguments = await ArgumentParser.parse( + pack, ); - add(args.manager)(pack)("@nestia/core", false); - add(args.manager)(pack)("@nestia/sdk", true); - add(args.manager)(pack)("nestia", true); - const modulo: typeof import("@nestia/core/lib/executable/internal/CoreSetupWizard") = - await import( - process.cwd() + - "/node_modules/@nestia/core/lib/executable/internal/CoreSetupWizard" - ); - return modulo.CoreSetupWizard; + // INSTALL TYPESCRIPT + pack.install({ dev: true, modulo: "typescript" }); + args.project ??= (() => { + CommandExecutor.run("npx tsc --init", false); + return (args.project = "tsconfig.json"); + })(); + pack.install({ dev: true, modulo: "ts-node" }); + + // INSTALL COMPILER + pack.install({ dev: true, modulo: args.compiler }); + if (args.compiler === "ts-patch") { + await pack.save((data) => { + data.scripts ??= {}; + if (typeof data.scripts.prepare === "string") + data.scripts.prepare = + "ts-patch install && " + data.scripts.prepare; + else data.scripts.prepare = "ts-patch install"; + }); + CommandExecutor.run("npm run prepare", false); + } + + // INSTALL AND CONFIGURE TYPIA + pack.install({ dev: false, modulo: "typia" }); + pack.install({ dev: false, modulo: "@nestia/core" }); + pack.install({ dev: true, modulo: "@nestia/sdk" }); + pack.install({ dev: true, modulo: "nestia" }); + await PluginConfigurator.configure(pack, args); } } - -const add = - (manager: string) => - (pack: any) => - (modulo: string, devOnly: boolean): void => { - const exists: boolean = - (devOnly === false - ? !!pack.dependencies && !!pack.dependencies[modulo] - : !!pack.devDependencies && !!pack.devDependencies[modulo]) && - fs.existsSync("node_modules/" + modulo); - const middle: string = - manager === "yarn" - ? `add${devOnly ? " -D" : ""}` - : `install ${devOnly ? "--save-dev" : "--save"}`; - if (exists === false) execute(`${manager} ${middle} ${modulo}`); - }; - -const halt = - (closer: () => any) => - (desc: string): never => { - closer(); - console.error(desc); - process.exit(-1); - }; - -function execute(command: string): void { - console.log(command); - cp.execSync(command, { stdio: "inherit" }); -} diff --git a/src/index.ts b/src/index.ts index 67e026780..c1cb0e944 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,4 @@ #!/usr/bin/env node -import { CommandParser } from "./CommandParser"; import { NestiaSetupWizard } from "./NestiaSetupWizard"; import { NestiaStarter } from "./NestiaStarter"; @@ -7,7 +6,7 @@ const USAGE = `Wrong command has been detected. Use like below: npx nestia [command] [options?] - 1. npx nestia start ---manager (npm|pnpm|yarn) + 1. npx nestia start --manager (npm|pnpm|yarn) - npx nestia start project - npx nestia start project --manager pnpm 2. npx nestia setup \\ @@ -37,31 +36,13 @@ function halt(desc: string): never { process.exit(-1); } -async function setup(): Promise { - const options: Record = CommandParser.parse( - process.argv.slice(3), - ); - const manager: string = options.manager ?? "npm"; - const compiler: string = options.compiler ?? "ttypescript"; - const project: string = options.project ?? "tsconfig.json"; - - if ( - (compiler !== "ttypescript" && compiler !== "ts-patch") || - (manager !== "npm" && manager !== "pnpm" && manager !== "yarn") - ) - halt(USAGE); - else if (compiler === "ttypescript") - await NestiaSetupWizard.ttypescript({ manager, project }); - else await NestiaSetupWizard.tsPatch({ manager, project }); -} - async function main(): Promise { const type: string | undefined = process.argv[2]; const argv: string[] = process.argv.slice(3); if (type === "start") await NestiaStarter.start((msg) => halt(msg ?? USAGE))(argv); - else if (type === "setup") await setup(); + else if (type === "setup") await NestiaSetupWizard.setup(); else if ( type === "dependencies" || type === "init" || diff --git a/src/internal/ArgumentParser.ts b/src/internal/ArgumentParser.ts new file mode 100644 index 000000000..cc8fd9728 --- /dev/null +++ b/src/internal/ArgumentParser.ts @@ -0,0 +1,144 @@ +import type CommanderModule from "commander"; +import fs from "fs"; +import type * as InquirerModule from "inquirer"; +import path from "path"; + +import { PackageManager } from "./PackageManager"; + +export namespace ArgumentParser { + export interface IArguments { + compiler: "ts-patch" | "ttypescript"; + manager: "npm" | "pnpm" | "yarn"; + project: string | null; + } + + export async function parse(pack: PackageManager): Promise { + // INSTALL TEMPORARY PACKAGES + const newbie = { + commander: pack.install({ + dev: true, + modulo: "commander", + version: "10.0.0", + silent: true, + }), + inquirer: pack.install({ + dev: true, + modulo: "inquirer", + version: "8.2.5", + silent: true, + }), + }; + + // TAKE OPTIONS + const output: IArguments | Error = await (async () => { + try { + return await _Parse(pack); + } catch (error) { + return error as Error; + } + })(); + + // REMOVE TEMPORARY PACKAGES + if (newbie.commander) pack.erase({ modulo: "commander", silent: true }); + if (newbie.inquirer) pack.erase({ modulo: "inquirer", silent: true }); + + // RETURNS + if (output instanceof Error) throw output; + return output; + } + + async function _Parse(pack: PackageManager): Promise { + // PREPARE ASSETS + const { createPromptModule }: typeof InquirerModule = await import( + path.join(pack.directory, "node_modules", "inquirer") + ); + const { program }: typeof CommanderModule = await import( + path.join(pack.directory, "node_modules", "commander") + ); + + program.option("--compiler [compiler]", "compiler type"); + program.option("--manager [manager", "package manager"); + program.option("--project [project]", "tsconfig.json file location"); + + // INTERNAL PROCEDURES + const questioned = { value: false }; + const action = ( + closure: (options: Partial) => Promise, + ) => { + return new Promise((resolve, reject) => { + program.action(async (options) => { + try { + resolve(await closure(options)); + } catch (exp) { + reject(exp); + } + }); + program.parseAsync().catch(reject); + }); + }; + const select = + (name: string) => + (message: string) => + async ( + choices: Choice[], + ): Promise => { + questioned.value = true; + return ( + await createPromptModule()({ + type: "list", + name: name, + message: message, + choices: choices, + }) + )[name]; + }; + const configure = async () => { + const fileList: string[] = await ( + await fs.promises.readdir(process.cwd()) + ).filter( + (str) => + str.substring(0, 8) === "tsconfig" && + str.substring(str.length - 5) === ".json", + ); + if (fileList.length === 0) { + if (process.cwd() !== pack.directory) + throw new Error(`Unable to find "tsconfig.json" file.`); + return null; + } else if (fileList.length === 1) return fileList[0]; + return select("tsconfig")("TS Config File")(fileList); + }; + + // DO CONSTRUCT + return action(async (options) => { + if (options.compiler === undefined) { + console.log(COMPILER_DESCRIPTION); + options.compiler = await select("compiler")(`Compiler`)( + pack.data.scripts?.build === "nest build" + ? ["ts-patch" as const, "ttypescript" as const] + : ["ttypescript" as const, "ts-patch" as const], + ); + } + options.manager ??= await select("manager")("Package Manager")([ + "npm" as const, + "pnpm" as const, + "yarn" as const, + ]); + pack.manager = options.manager; + options.project ??= await configure(); + + if (questioned.value) console.log(""); + return options as IArguments; + }); + } +} + +const COMPILER_DESCRIPTION = [ + `About compiler, if you adapt "ttypescript", you should use "ttsc" instead.`, + ``, + `Otherwise, you choose "ts-patch", you can use the original "tsc" command.`, + `However, the "ts-patch" hacks "node_modules/typescript" source code.`, + `Also, whenever update "typescript", you've to run "npm run prepare" command.`, + ``, + `By the way, when using "@nest/cli", you must just choose "ts-patch".`, + ``, +].join("\n"); diff --git a/src/internal/CommandExecutor.ts b/src/internal/CommandExecutor.ts new file mode 100644 index 000000000..e3e654a00 --- /dev/null +++ b/src/internal/CommandExecutor.ts @@ -0,0 +1,8 @@ +import cp from "child_process"; + +export namespace CommandExecutor { + export function run(str: string, silent: boolean): void { + if (silent === false) console.log(str); + cp.execSync(str, { stdio: "ignore" }); + } +} diff --git a/src/internal/PackageManager.ts b/src/internal/PackageManager.ts new file mode 100644 index 000000000..b2f3174a5 --- /dev/null +++ b/src/internal/PackageManager.ts @@ -0,0 +1,99 @@ +import fs from "fs"; +import path from "path"; + +import { CommandExecutor } from "./CommandExecutor"; + +export class PackageManager { + public manager: string = "npm"; + public get file(): string { + return path.join(this.directory, "package.json"); + } + + public static async mount(): Promise { + const location: string | null = await find(process.cwd()); + if (location === null) + throw new Error(`Unable to find "package.json" file`); + + return new PackageManager( + location, + await this.load(path.join(location, "package.json")), + ); + } + + public async save(modifier: (data: Package.Data) => void): Promise { + const content: string = await fs.promises.readFile(this.file, "utf8"); + this.data = JSON.parse(content); + modifier(this.data); + + return fs.promises.writeFile( + this.file, + JSON.stringify(this.data, null, 2), + "utf8", + ); + } + + public install(props: { + dev: boolean; + silent?: boolean; + modulo: string; + version?: string; + }): boolean { + const container = props.dev + ? this.data.devDependencies + : this.data.dependencies; + if ( + !!container?.[props.modulo] && + fs.existsSync( + path.join(this.directory, "node_modules", props.modulo), + ) + ) + return false; + + const middle: string = + this.manager === "yarn" + ? `add${props.dev ? " -D" : ""}` + : `install ${props.dev ? "--save-dev" : "--save"}`; + CommandExecutor.run( + `${this.manager} ${middle} ${props.modulo}${ + props.version ? `@${props.version}` : "" + }`, + !!props.silent, + ); + return true; + } + + public erase(props: { modulo: string; silent?: boolean }): void { + const middle: string = this.manager === "yarn" ? "remove" : "uninstall"; + CommandExecutor.run( + `${this.manager} ${middle} ${props.modulo}`, + !!props.silent, + ); + } + + private constructor( + public readonly directory: string, + public data: Package.Data, + ) {} + + private static async load(file: string): Promise { + const content: string = await fs.promises.readFile(file, "utf8"); + return JSON.parse(content); + } +} +export namespace Package { + export interface Data { + scripts?: Record; + dependencies?: Record; + devDependencies?: Record; + } +} + +async function find( + directory: string = process.cwd(), + depth: number = 0, +): Promise { + const location: string = path.join(directory, "package.json"); + if (fs.existsSync(location)) return directory; + else if (depth > 1) return null; + return find(path.join(directory, ".."), depth + 1); +} diff --git a/src/internal/PluginConfigurator.ts b/src/internal/PluginConfigurator.ts new file mode 100644 index 000000000..0ad528516 --- /dev/null +++ b/src/internal/PluginConfigurator.ts @@ -0,0 +1,128 @@ +import type Comment from "comment-json"; +import fs from "fs"; +import path from "path"; + +import { ArgumentParser } from "./ArgumentParser"; +import { PackageManager } from "./PackageManager"; + +export namespace PluginConfigurator { + export async function configure( + pack: PackageManager, + args: ArgumentParser.IArguments, + ): Promise { + // INSTALL COMMENT-JSON + const installed: boolean = pack.install({ + dev: true, + modulo: "comment-json", + version: "4.2.3", + silent: true, + }); + + // DO CONFIGURE + const error: Error | null = await (async () => { + try { + await _Configure(pack, args); + return null; + } catch (exp) { + return exp as Error; + } + })(); + + // REMOVE IT + if (installed) + pack.erase({ + modulo: "comment-json", + silent: true, + }); + if (error !== null) throw error; + } + + async function _Configure( + pack: PackageManager, + args: ArgumentParser.IArguments, + ): Promise { + // GET COMPILER-OPTIONS + const Comment: typeof import("comment-json") = await import( + path.join(pack.directory, "node_modules", "comment-json") + ); + + const config: Comment.CommentObject = Comment.parse( + await fs.promises.readFile(args.project!, "utf8"), + ) as Comment.CommentObject; + const compilerOptions = config.compilerOptions as + | Comment.CommentObject + | undefined; + if (compilerOptions === undefined) + throw new Error( + `${args.project} file does not have "compilerOptions" property.`, + ); + + // PREPARE PLUGINS + const plugins: Comment.CommentArray = (() => { + const plugins = compilerOptions.plugins as + | Comment.CommentArray + | undefined; + if (plugins === undefined) + return (compilerOptions.plugins = [] as any); + else if (!Array.isArray(plugins)) + throw new Error( + `"plugins" property of ${args.project} must be array type.`, + ); + return plugins; + })(); + + // CHECK WHETHER CONFIGURED + const strict: boolean = compilerOptions.strict === true; + const core: Comment.CommentObject | undefined = plugins.find( + (p) => + typeof p === "object" && + p !== null && + p.transform === "@nestia/core/lib/transform", + ); + const typia: Comment.CommentObject | undefined = plugins.find( + (p) => + typeof p === "object" && + p !== null && + p.transform === "typia/lib/transform", + ); + if (strict && !!core && !!typia) return; + + // DO CONFIGURE + compilerOptions.strict = true; + if (core === undefined) + plugins.push( + Comment.parse(`{ + "transform": "@nestia/core/lib/transform", + /** + * Validate request body. + * + * - "assert": Use typia.assert() function + * - "is": Use typia.is() function + * - "validate": Use typia.validate() function + */ + "validate": "assert", + + /** + * Validate JSON typed response body. + * + * - null: Just use JSON.stringify() function, without boosting + * - "stringify": Use typia.stringify() function, but dangerous + * - "assert": Use typia.assertStringify() function + * - "is": Use typia.isStringify() function + * - "validate": Use typia.validateStringify() function + */ + "stringify": "is" + }`) as Comment.CommentObject, + ); + if (typia === undefined) + plugins.push( + Comment.parse( + `{ "transform": "typia/lib/transform" }`, + ) as Comment.CommentObject, + ); + await fs.promises.writeFile( + args.project!, + Comment.stringify(config, null, 2), + ); + } +} From 9e1c47816e2f9cf2a81f724e41fdb2792c6df53d Mon Sep 17 00:00:00 2001 From: Jeongho Nam Date: Fri, 17 Feb 2023 03:38:55 +0900 Subject: [PATCH 2/2] Fix github actions bug --- packages/core/test/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/test/tsconfig.json b/packages/core/test/tsconfig.json index 2e2dc0219..d91e23c81 100644 --- a/packages/core/test/tsconfig.json +++ b/packages/core/test/tsconfig.json @@ -105,7 +105,8 @@ "skipLibCheck": true, /* Skip type checking all .d.ts files. */ "plugins": [ { "transform": "../src/transform.ts" }, - { "transform": "typia/lib/transform" } + { "transform": "typescript-transform-paths" }, + { "transform": "typia/lib/transform" }, ] } }