From d2e25fce4189aca0c0913d87178e3355c32eba79 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 27 Aug 2024 14:16:37 +0100 Subject: [PATCH 01/21] feat(cli): rename from `executable` to `cli` --- src/{executable => cli}/TypiaGenerateWizard.ts | 0 src/{executable => cli}/TypiaPatchWizard.ts | 0 src/{executable => cli}/TypiaSetupWizard.ts | 0 src/{executable => cli}/setup/ArgumentParser.ts | 0 src/{executable => cli}/setup/CommandExecutor.ts | 0 src/{executable => cli}/setup/FileRetriever.ts | 0 src/{executable => cli}/setup/PackageManager.ts | 0 src/{executable => cli}/setup/PluginConfigurator.ts | 0 src/{executable => cli}/typia.ts | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename src/{executable => cli}/TypiaGenerateWizard.ts (100%) rename src/{executable => cli}/TypiaPatchWizard.ts (100%) rename src/{executable => cli}/TypiaSetupWizard.ts (100%) rename src/{executable => cli}/setup/ArgumentParser.ts (100%) rename src/{executable => cli}/setup/CommandExecutor.ts (100%) rename src/{executable => cli}/setup/FileRetriever.ts (100%) rename src/{executable => cli}/setup/PackageManager.ts (100%) rename src/{executable => cli}/setup/PluginConfigurator.ts (100%) rename src/{executable => cli}/typia.ts (100%) diff --git a/src/executable/TypiaGenerateWizard.ts b/src/cli/TypiaGenerateWizard.ts similarity index 100% rename from src/executable/TypiaGenerateWizard.ts rename to src/cli/TypiaGenerateWizard.ts diff --git a/src/executable/TypiaPatchWizard.ts b/src/cli/TypiaPatchWizard.ts similarity index 100% rename from src/executable/TypiaPatchWizard.ts rename to src/cli/TypiaPatchWizard.ts diff --git a/src/executable/TypiaSetupWizard.ts b/src/cli/TypiaSetupWizard.ts similarity index 100% rename from src/executable/TypiaSetupWizard.ts rename to src/cli/TypiaSetupWizard.ts diff --git a/src/executable/setup/ArgumentParser.ts b/src/cli/setup/ArgumentParser.ts similarity index 100% rename from src/executable/setup/ArgumentParser.ts rename to src/cli/setup/ArgumentParser.ts diff --git a/src/executable/setup/CommandExecutor.ts b/src/cli/setup/CommandExecutor.ts similarity index 100% rename from src/executable/setup/CommandExecutor.ts rename to src/cli/setup/CommandExecutor.ts diff --git a/src/executable/setup/FileRetriever.ts b/src/cli/setup/FileRetriever.ts similarity index 100% rename from src/executable/setup/FileRetriever.ts rename to src/cli/setup/FileRetriever.ts diff --git a/src/executable/setup/PackageManager.ts b/src/cli/setup/PackageManager.ts similarity index 100% rename from src/executable/setup/PackageManager.ts rename to src/cli/setup/PackageManager.ts diff --git a/src/executable/setup/PluginConfigurator.ts b/src/cli/setup/PluginConfigurator.ts similarity index 100% rename from src/executable/setup/PluginConfigurator.ts rename to src/cli/setup/PluginConfigurator.ts diff --git a/src/executable/typia.ts b/src/cli/typia.ts similarity index 100% rename from src/executable/typia.ts rename to src/cli/typia.ts From b16cf8ed47fde01719f1ae07d84586cdbf878a04 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 27 Aug 2024 14:17:39 +0100 Subject: [PATCH 02/21] feat(cli): create new bin endpoint --- .gitignore | 1 + bin/typia.mjs | 5 +++++ package.json | 6 ++++-- 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 bin/typia.mjs diff --git a/.gitignore b/.gitignore index edba8dc08c..5282d49496 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ bin/ +!/bin lib/ node_modules/ diff --git a/bin/typia.mjs b/bin/typia.mjs new file mode 100644 index 0000000000..3290f93784 --- /dev/null +++ b/bin/typia.mjs @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +import { cli } from '../lib/cli/index.js' + +await cli() diff --git a/package.json b/package.json index 0adc5a2ec7..e7e1f7c941 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "typings": "lib/index.d.ts", "module": "lib/index.mjs", "bin": { - "typia": "./lib/executable/typia.js" + "typia": "./bin/typia.mjs" }, "tsp": { "tscOptions": { @@ -103,7 +103,9 @@ "README.md", "package.json", "lib", + "bin", "src" ], "private": true -} \ No newline at end of file +} + From 7d1dec1df18c0418bdf8c7f64beb2a69513ba085 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 27 Aug 2024 15:25:36 +0100 Subject: [PATCH 03/21] feat(cli): build as esm format --- package.json | 3 +-- tsconfig.cli.json | 9 +++++++++ tsconfig.json | 3 ++- 3 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 tsconfig.cli.json diff --git a/package.json b/package.json index e7e1f7c941..92cb244890 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "test:bun": "bun run deploy/bun.ts", "test:template": "npm run --tag test --template", "-------------------------------------------------": "", - "build": "rimraf lib && tsc && rollup -c", + "build": "rimraf lib && tsc && tsc -p ./tsconfig.cli.json && rollup -c", "dev": "rimraf lib && tsc --watch", "eslint": "eslint ./**/*.ts", "eslint:fix": "eslint ./**/*.ts --fix", @@ -106,6 +106,5 @@ "bin", "src" ], - "private": true } diff --git a/tsconfig.cli.json b/tsconfig.cli.json new file mode 100644 index 0000000000..5c03b36316 --- /dev/null +++ b/tsconfig.cli.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": false, + "sourceMap": false + }, + "include": ["src/cli"], + "exclude": [] +} diff --git a/tsconfig.json b/tsconfig.json index 2e9fb09c29..f3914ece9a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -101,5 +101,6 @@ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, - "include": ["src"] + "include": ["src"], + "exclude": ["src/cli"] } From f5855e3408e4fd8e3bba7a466e207d1693b83e07 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 27 Aug 2024 15:26:40 +0100 Subject: [PATCH 04/21] feat(cli): create parent command with `cleye` --- package.json | 4 ++-- src/cli/index.ts | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 src/cli/index.ts diff --git a/package.json b/package.json index 92cb244890..f365c5906f 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "homepage": "https://typia.io", "dependencies": { "@samchon/openapi": "^0.4.9", + "cleye": "^1.3.2", "commander": "^10.0.0", "comment-json": "^4.2.3", "inquirer": "^8.2.5", @@ -105,6 +106,5 @@ "lib", "bin", "src" - ], + ] } - diff --git a/src/cli/index.ts b/src/cli/index.ts new file mode 100644 index 0000000000..86b45a89e4 --- /dev/null +++ b/src/cli/index.ts @@ -0,0 +1,10 @@ +import { cli as cleye } from 'cleye' + +export async function cli(){ + const argv = cleye({ + name: "typia", + version: "1.0.0", + description: "CLI for Typia operations", + }) + console.log(argv) +} From 6e83d3d73565581a09a16e74468afdef2551231f Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 27 Aug 2024 15:28:18 +0100 Subject: [PATCH 05/21] feat(cli/utils): create logger --- package.json | 1 + src/cli/utils/logger.ts | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 src/cli/utils/logger.ts diff --git a/package.json b/package.json index f365c5906f..10c2566818 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "cleye": "^1.3.2", "commander": "^10.0.0", "comment-json": "^4.2.3", + "consola": "^3.2.3", "inquirer": "^8.2.5", "randexp": "^0.5.3" }, diff --git a/src/cli/utils/logger.ts b/src/cli/utils/logger.ts new file mode 100644 index 0000000000..2c5be55c76 --- /dev/null +++ b/src/cli/utils/logger.ts @@ -0,0 +1,3 @@ +import { type ConsolaInstance, consola } from "consola"; + +export const logger: ConsolaInstance = consola.withTag("typia-cli"); From 9ac71b960113f085d7f0867a6fb8bc72bb470150 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 27 Aug 2024 15:31:03 +0100 Subject: [PATCH 06/21] feat(cli/utils): create command executor --- src/cli/utils/command.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/cli/utils/command.ts diff --git a/src/cli/utils/command.ts b/src/cli/utils/command.ts new file mode 100644 index 0000000000..7abfb29ead --- /dev/null +++ b/src/cli/utils/command.ts @@ -0,0 +1,6 @@ +import cp from "child_process"; + +export function run(str: string): void { + console.log(`\n$ ${str}`); + cp.execSync(str, { stdio: "inherit" }); +} From ed7cdc7148366c10f36974719abb7ed862e87cbf Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 27 Aug 2024 15:31:18 +0100 Subject: [PATCH 07/21] feat(cli/utils): create message fucnctions --- src/cli/utils/message.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/cli/utils/message.ts diff --git a/src/cli/utils/message.ts b/src/cli/utils/message.ts new file mode 100644 index 0000000000..85b9db4598 --- /dev/null +++ b/src/cli/utils/message.ts @@ -0,0 +1,13 @@ +import { logger } from "./logger"; + +/** + * throw an error message and exit the process + */ +export function bail(message: string): never { + logger.error(message); + process.exit(1); +} + +export function wizard(): void { + logger.box("Typia Setup Wizard"); +} From aba8bf5fcf4a852d581b25403d382c6b22f722ce Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 27 Aug 2024 15:41:28 +0100 Subject: [PATCH 08/21] feat(cli): add patch command --- src/cli/index.ts | 9 +++++++ src/cli/subcommands/patch.ts | 47 ++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 src/cli/subcommands/patch.ts diff --git a/src/cli/index.ts b/src/cli/index.ts index 86b45a89e4..2acd2009e0 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -1,10 +1,19 @@ import { cli as cleye } from 'cleye' +import { patch } from './subcommands/patch'; export async function cli(){ const argv = cleye({ name: "typia", version: "1.0.0", description: "CLI for Typia operations", + + commands: [ + patch, + ], + }) + + const { flags } = argv; + console.log(argv) } diff --git a/src/cli/subcommands/patch.ts b/src/cli/subcommands/patch.ts new file mode 100644 index 0000000000..a2eb50296d --- /dev/null +++ b/src/cli/subcommands/patch.ts @@ -0,0 +1,47 @@ +import { command } from 'cleye'; +import fs from "node:fs/promises"; + +import { logger } from "../utils/logger"; + +const FROM_WITH_COMMENT = `var defaultJSDocParsingMode = 2 /* ParseForTypeErrors */`; +const TO_WITH_COMMENT = `var defaultJSDocParsingMode = 0 /* ParseAll */`; +const FROM_ONLY = `var defaultJSDocParsingMode = 2`; +const TO_ONLY = `var defaultJSDocParsingMode = 0`; + +export const patch = command({ + name: "patch", + + aliases: ["p"], + + help: { + description: "Extra patching for TypeScript", + } +}, async () => { + logger.info( + [ + `Since TypeScript v5.3 update, "tsc" no more parses JSDoc comments.`, + ``, + `Therefore, "typia" revives the JSDoc parsing feature by patching "tsc".`, + ``, + `This is a temporary feature of "typia", and it would be removed when "ts-patch" being updated.`, + ].join("\n"), + ); + + await executePatch(); + logger.success("Patched TypeScript"); + } +); + +export async function executePatch(): Promise { + const location: string = require.resolve("typescript/lib/tsc.js"); + const content: string = await fs.readFile(location, "utf8"); + if (!content.includes(FROM_WITH_COMMENT)) { + await fs.writeFile( + location, + content.replace(FROM_WITH_COMMENT, TO_WITH_COMMENT), + "utf8", + ); + } else if (!content.includes(FROM_ONLY)) { + await fs.writeFile(location, content.replace(FROM_ONLY, TO_ONLY), "utf8"); + } +} From f4bb0307e81d2655c52a676929ded66ddebc12b2 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 27 Aug 2024 15:44:53 +0100 Subject: [PATCH 09/21] feat(cli/utils): add confFiles --- package.json | 3 ++- src/cli/utils/confFiles.ts | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 src/cli/utils/confFiles.ts diff --git a/package.json b/package.json index 10c2566818..031fc15d0f 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,8 @@ "comment-json": "^4.2.3", "consola": "^3.2.3", "inquirer": "^8.2.5", - "randexp": "^0.5.3" + "randexp": "^0.5.3", + "tinyglobby": "^0.2.5" }, "peerDependencies": { "typescript": ">=4.8.0 <5.6.0" diff --git a/src/cli/utils/confFiles.ts b/src/cli/utils/confFiles.ts new file mode 100644 index 0000000000..2e2270aa94 --- /dev/null +++ b/src/cli/utils/confFiles.ts @@ -0,0 +1,30 @@ +import process from "node:process"; +import { glob } from "tinyglobby"; + +import { logger } from "./logger"; +import { bail } from "./message"; + +export async function findTsConfig( + { cwd }: { cwd: string } = { cwd: process.cwd() }, +): Promise { + const tsConfigs = await glob(["tsconfig.json", "tsconfig.*.json"], { cwd }); + + if (tsConfigs.length === 0) { + bail("tsconfig.json not found"); + } + + if (tsConfigs.length === 1) { + const tsconfig = tsConfigs.at(0); + if (tsconfig != null) { + return tsconfig; + } + } + + return await logger.prompt( + "Multiple tsconfig.json files found. Please specify the one to use:", + { + type: "select", + options: tsConfigs, + }, + ); +} From 391452c34e6f3c7e2dae5772f6c5c08d26e04366 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 27 Aug 2024 15:46:01 +0100 Subject: [PATCH 10/21] feat(cli/utils): add fs.ts --- src/cli/utils/fs.ts | 105 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 src/cli/utils/fs.ts diff --git a/src/cli/utils/fs.ts b/src/cli/utils/fs.ts new file mode 100644 index 0000000000..f9b85cb9c6 --- /dev/null +++ b/src/cli/utils/fs.ts @@ -0,0 +1,105 @@ +// @see https://github.com/ryoppippi/bumpp/blob/e93efe88bba42bd0875f12f1c10744f41b732b6e/src/fs.ts +import * as cj from "comment-json"; +import fs from "node:fs"; +import fsPromises from "node:fs/promises"; +import path from "node:path"; +import process from "node:process"; + +/** + * Find a file in the directory hierarchy + */ +export async function findUp( + name: string | string[], + { cwd }: { cwd: string | undefined } = { cwd: process.cwd() }, +): Promise { + let directory = path.resolve(cwd ?? process.cwd()); + const { root } = path.parse(directory); + const names = [name].flat(); + + while (directory && directory !== root) { + for (const name of names) { + const filePath = path.join(directory, name); + + try { + const stats = await fsPromises.stat(filePath); + if (stats.isFile()) { + return filePath; + } + } catch {} + } + + directory = path.dirname(directory); + } + return; +} + +/** + * Describes a plain-text file. + */ +export interface TextFile { + path: string; + data: string; +} + +/** + * Describes a JSON file. + */ +interface JsonFile { + path: Readonly; + data: T & cj.CommentJSONValue; +} + +/** + * Reads a JSON file and returns the parsed data. + * This functions supports JSON/JSONC/JSON with comments. + */ +export async function readJsonFile( + name: string, + cwd: string, +): Promise> { + const file = await readTextFile(name, cwd); + const data = cj.parse(file.data) as T & cj.CommentObject; + + return { ...file, data }; +} + +/** + * Writes the given data to the specified JSON/JSONC file. + */ +export async function writeJsonFile(file: JsonFile): Promise { + const newJSON = cj.stringify(file.data, null, 2); + + return writeTextFile({ ...file, data: newJSON }); +} + +/** + * Reads a text file and returns its contents. + */ +export function readTextFile(name: string, cwd: string): Promise { + return new Promise((resolve, reject) => { + const filePath = path.isAbsolute(name) ? name : path.resolve(cwd, name); + + fs.readFile(filePath, "utf8", (err, text) => { + if (err) { + reject(err); + } else { + resolve({ + path: filePath, + data: text, + }); + } + }); + }); +} + +/** + * Writes the given text to the specified file. + */ +export function writeTextFile(file: TextFile): Promise { + return new Promise((resolve, reject) => { + fs.writeFile(file.path, file.data, (err) => { + if (err) reject(err); + else resolve(); + }); + }); +} From 01486f7d62871ea80e3f7622c8a3e6540afae3b9 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 27 Aug 2024 15:52:08 +0100 Subject: [PATCH 11/21] feat(cli): add generate command --- src/cli/index.ts | 2 ++ src/cli/subcommands/generate.ts | 42 +++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 src/cli/subcommands/generate.ts diff --git a/src/cli/index.ts b/src/cli/index.ts index 2acd2009e0..c128932d55 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -1,5 +1,6 @@ import { cli as cleye } from 'cleye' import { patch } from './subcommands/patch'; +import { generate } from './subcommands/generate'; export async function cli(){ const argv = cleye({ @@ -9,6 +10,7 @@ export async function cli(){ commands: [ patch, + generate, ], }) diff --git a/src/cli/subcommands/generate.ts b/src/cli/subcommands/generate.ts new file mode 100644 index 0000000000..448e3a70f7 --- /dev/null +++ b/src/cli/subcommands/generate.ts @@ -0,0 +1,42 @@ +import { command } from 'cleye'; +import { TypiaProgrammer } from "../../programmers/TypiaProgrammer"; + +import { findTsConfig } from "../utils/confFiles"; +import { logger } from "../utils/logger"; +import { bail } from "../utils/message"; + +export const generate = command({ + name: "generate", + + flags: { + input: { + type: String, + description: "input directory", + }, + output: { + type: String, + description: "output directory", + }, + project: { + type: String, + description: "tsconfig.json file path (e.g. ./tsconfig.test.json)", + }, + }, + + help: { + description: "Generate Typia files", + } +}, async (argv) => { + let { input, output, project } = argv.flags; + + input ??= await logger.prompt("input directory", { type: "text" }); + output ??= await logger.prompt("output directory", { type: "text" }); + project ??= await findTsConfig(); + + if (project == null) { + bail("tsconfig.json not found"); + } + + await TypiaProgrammer.build({ input, output, project }); + }, +); From 080de2440a14ebcaa5bb861398b9a7e85037ce3d Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:30:11 +0100 Subject: [PATCH 12/21] feat(cli): add setup command --- package.json | 1 + src/cli/index.ts | 2 + src/cli/subcommands/setup.ts | 152 +++++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 src/cli/subcommands/setup.ts diff --git a/package.json b/package.json index 031fc15d0f..8598f52d2c 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "comment-json": "^4.2.3", "consola": "^3.2.3", "inquirer": "^8.2.5", + "package-manager-detector": "^0.2.0", "randexp": "^0.5.3", "tinyglobby": "^0.2.5" }, diff --git a/src/cli/index.ts b/src/cli/index.ts index c128932d55..9efaac2d35 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -1,6 +1,7 @@ import { cli as cleye } from 'cleye' import { patch } from './subcommands/patch'; import { generate } from './subcommands/generate'; +import { setup } from './subcommands/setup'; export async function cli(){ const argv = cleye({ @@ -11,6 +12,7 @@ export async function cli(){ commands: [ patch, generate, + setup, ], }) diff --git a/src/cli/subcommands/setup.ts b/src/cli/subcommands/setup.ts new file mode 100644 index 0000000000..0fc0016a2f --- /dev/null +++ b/src/cli/subcommands/setup.ts @@ -0,0 +1,152 @@ +import { command } from 'cleye'; +import process from "node:process"; +import { existsSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { detect } from "package-manager-detector"; +import { AGENTS } from "package-manager-detector/constants"; +import { resolveCommand } from 'package-manager-detector/commands' + +import { run } from "../utils/command"; +import { findTsConfig } from "../utils/confFiles"; +import { findUp, readJsonFile, writeJsonFile } from "../utils/fs"; +import { logger } from "../utils/logger"; +import { bail } from "../utils/message"; + +const TSPATCH_COMMAND = `ts-patch install`; +const TYPIA_PATCH_COMMAND = `typia patch`; +const TYPIA_TRANSFORM = `typia/lib/transform`; + +/** package.json type */ +interface PackageJson { + scripts?: Record; +} + +/** tsconfig.json type */ +interface TSConfig { + compilerOptions?: { + strictNullChecks?: boolean; + strict?: boolean; + plugins?: { + transform: string; + }[]; + }; +} + +/** dependency type */ +interface Dependency { + dev: boolean; + modulo: string; + version: string; +} + +export const setup = command({ + name: "setup", + + flags: { + project: { + type: String, + description: "tsconfig.json file path (e.g. ./tsconfig.test.json)", + }, + }, + + help: { + description: "Setup Typia", + } +}, async (argv) => { + const { flags } = argv; + const cwd = process.cwd(); + const manager = await detect({ cwd }); + let agent = manager?.agent; + + if (agent == null) { + const selected = await logger.prompt("Select a package manager", { + initial: "npm", + options: AGENTS, + }) as typeof AGENTS[number]; + agent = selected; + } + + /* yarn@berry is not supported */ + if (agent === "yarn@berry") { + bail("yarn@berry is not supported."); + } + + /* install dependencies */ + for (const dep of DEPENDENCIES) { + const addArgs= [ + `${dep.modulo}@${dep.version}`, + dep.dev ? "-D" : "", + (agent==='pnpm' || agent==='pnpm@6') && existsSync(resolve(cwd, 'pnpm-workspace.yaml')) ? '-w' : '' + ] + const { command, args } = resolveCommand(agent, 'add', addArgs)!; + run(`${command} ${args.join(" ")}`); + } + + /* === prepare package.json === */ + { + const path = await findUp("package.json", { cwd }); + if (path == null) { + bail("package.json not found."); + } + const json = await readJsonFile(path, cwd); + + let prepare = ( + (json.data?.scripts?.prepare as string | undefined) ?? "" + ).trim(); + + const FULL_COMMAND = `${TSPATCH_COMMAND} && ${TYPIA_PATCH_COMMAND}`; + + /* if ony `ts-patch install` is found, add `typia patch` */ + prepare.replace(TSPATCH_COMMAND, FULL_COMMAND); + + /* if prepare script is empty, set it to `typia patch` */ + if (prepare === "") { + prepare = FULL_COMMAND; + } + + /* if prepare script does not contain `typia patch`, add it */ + if (prepare !== FULL_COMMAND && !prepare.includes(FULL_COMMAND)) { + prepare = `${FULL_COMMAND} && ${prepare}`; + } + + /* update prepare script */ + json.data.scripts = { ...(json.data.scripts ?? {}), prepare }; + await writeJsonFile(json); + } + + /* === prepare tsconfig.json === */ + { + const tsConfigPath = flags.project ?? (await findTsConfig({ cwd })); + /* if tsconfig.json is not found, create it */ + if (tsConfigPath == null) { + const { command, args } = resolveCommand(agent, 'execute', ['tsc --init'])!; + run(`${command} ${args.join(" ")}`); + } + + const tsConfig = await readJsonFile(tsConfigPath, cwd); + + if (tsConfig.data.compilerOptions == null) { + tsConfig.data.compilerOptions = {}; + } + + tsConfig.data.compilerOptions.strictNullChecks = true; + tsConfig.data.compilerOptions.strict = true; + + tsConfig.data.compilerOptions.plugins = [ + { transform: TYPIA_TRANSFORM }, + ...(tsConfig.data.compilerOptions.plugins ?? []), + ]; + await writeJsonFile(tsConfig); + } + + /* === run prepare script === */ + const { command, args } = resolveCommand(agent, 'run', ['prepare'])!; + run(`${command} ${args.join(" ")}`); + }, +); + +/** dependencies to be installed */ +const DEPENDENCIES = [ + { dev: true, modulo: "typescript", version: "5.5.2" }, + { dev: true, modulo: "ts-patch", version: "latest" }, +] as const satisfies Dependency[]; From 0563d81b0189f313b7ac33b4acb90479a72654ab Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:32:08 +0100 Subject: [PATCH 13/21] feat(package.json): add cli run commands --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 8598f52d2c..a7cb29afb1 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,9 @@ "test:template": "npm run --tag test --template", "-------------------------------------------------": "", "build": "rimraf lib && tsc && tsc -p ./tsconfig.cli.json && rollup -c", + "cli": "node ./bin/typia.mjs", "dev": "rimraf lib && tsc --watch", + "dev:cli": "ts-node ./src/index.ts", "eslint": "eslint ./**/*.ts", "eslint:fix": "eslint ./**/*.ts --fix", "prettier": "prettier src --write", From 135c0544f0c31b122729655720e376f3d5cdede9 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:32:20 +0100 Subject: [PATCH 14/21] feat(cli): delete old commands --- src/cli/TypiaGenerateWizard.ts | 83 --------------- src/cli/TypiaPatchWizard.ts | 42 -------- src/cli/TypiaSetupWizard.ts | 151 ---------------------------- src/cli/setup/ArgumentParser.ts | 43 -------- src/cli/setup/CommandExecutor.ts | 8 -- src/cli/setup/FileRetriever.ts | 22 ---- src/cli/setup/PackageManager.ts | 86 ---------------- src/cli/setup/PluginConfigurator.ts | 69 ------------- src/cli/typia.ts | 55 ---------- 9 files changed, 559 deletions(-) delete mode 100644 src/cli/TypiaGenerateWizard.ts delete mode 100644 src/cli/TypiaPatchWizard.ts delete mode 100644 src/cli/TypiaSetupWizard.ts delete mode 100644 src/cli/setup/ArgumentParser.ts delete mode 100644 src/cli/setup/CommandExecutor.ts delete mode 100644 src/cli/setup/FileRetriever.ts delete mode 100644 src/cli/setup/PackageManager.ts delete mode 100644 src/cli/setup/PluginConfigurator.ts delete mode 100644 src/cli/typia.ts diff --git a/src/cli/TypiaGenerateWizard.ts b/src/cli/TypiaGenerateWizard.ts deleted file mode 100644 index f3c5e55ac4..0000000000 --- a/src/cli/TypiaGenerateWizard.ts +++ /dev/null @@ -1,83 +0,0 @@ -import fs from "fs"; - -import { TypiaProgrammer } from "../programmers/TypiaProgrammer"; - -import { ArgumentParser } from "./setup/ArgumentParser"; -import { PackageManager } from "./setup/PackageManager"; - -export namespace TypiaGenerateWizard { - export async function generate(): Promise { - console.log("----------------------------------------"); - console.log(" Typia Generate Wizard"); - console.log("----------------------------------------"); - - // LOAD PACKAGE.JSON INFO - const pack: PackageManager = await PackageManager.mount(); - const options: IArguments = await ArgumentParser.parse(pack)(inquiry); - await TypiaProgrammer.build(options); - } - - const inquiry: ArgumentParser.Inquiry = async ( - _pack, - command, - prompt, - action, - ) => { - // PREPARE ASSETS - command.option("--input [path]", "input directory"); - command.option("--output [directory]", "output directory"); - command.option("--project [project]", "tsconfig.json file location"); - - const questioned = { value: false }; - - const input = (name: string) => async (message: string) => { - const result = await prompt()({ - type: "input", - name, - message, - default: "", - }); - return result[name] as string; - }; - const select = - (name: string) => - (message: string) => - async (choices: Choice[]): Promise => { - questioned.value = true; - return ( - await prompt()({ - type: "list", - name: name, - message: message, - choices: choices, - }) - )[name]; - }; - const configure = async (): Promise => { - const files: string[] = await ( - await fs.promises.readdir(process.cwd()) - ).filter( - (str) => - str.substring(0, 8) === "tsconfig" && - str.substring(str.length - 5) === ".json", - ); - if (files.length === 0) - throw new URIError(`Unable to find "tsconfig.json" file.`); - else if (files.length === 1) return files[0]!; - return select("tsconfig")("TS Config File")(files); - }; - - return action(async (options) => { - options.input ??= await input("input")("input directory"); - options.output ??= await input("output")("output directory"); - options.project ??= await configure(); - return options as IArguments; - }); - }; - - export interface IArguments { - input: string; - output: string; - project: string; - } -} diff --git a/src/cli/TypiaPatchWizard.ts b/src/cli/TypiaPatchWizard.ts deleted file mode 100644 index ac31631c0a..0000000000 --- a/src/cli/TypiaPatchWizard.ts +++ /dev/null @@ -1,42 +0,0 @@ -import fs from "fs"; - -export namespace TypiaPatchWizard { - export const main = async (): Promise => { - console.log("----------------------------------------"); - console.log(" Typia Setup Wizard"); - console.log("----------------------------------------"); - console.log( - [ - `Since TypeScript v5.3 update, "tsc" no more parses JSDoc comments.`, - ``, - `Therefore, "typia" revives the JSDoc parsing feature by patching "tsc".`, - ``, - `This is a temporary feature of "typia", and it would be removed when "ts-patch" being updated.`, - ].join("\n"), - ); - - await patch(); - }; - - export const patch = async (): Promise => { - const location: string = require.resolve("typescript/lib/tsc.js"); - const content: string = await fs.promises.readFile(location, "utf8"); - if (content.indexOf(FROM_WITH_COMMENT) !== -1) - await fs.promises.writeFile( - location, - content.replace(FROM_WITH_COMMENT, TO_WITH_COMMENT), - "utf8", - ); - else if (content.indexOf(FROM_ONLY) !== -1) - await fs.promises.writeFile( - location, - content.replace(FROM_ONLY, TO_ONLY), - "utf8", - ); - }; -} - -const FROM_WITH_COMMENT = `var defaultJSDocParsingMode = 2 /* ParseForTypeErrors */`; -const TO_WITH_COMMENT = `var defaultJSDocParsingMode = 0 /* ParseAll */`; -const FROM_ONLY = `var defaultJSDocParsingMode = 2`; -const TO_ONLY = `var defaultJSDocParsingMode = 0`; diff --git a/src/cli/TypiaSetupWizard.ts b/src/cli/TypiaSetupWizard.ts deleted file mode 100644 index 6f4ba9319f..0000000000 --- a/src/cli/TypiaSetupWizard.ts +++ /dev/null @@ -1,151 +0,0 @@ -import fs from "fs"; - -import { ArgumentParser } from "./setup/ArgumentParser"; -import { CommandExecutor } from "./setup/CommandExecutor"; -import { PackageManager } from "./setup/PackageManager"; -import { PluginConfigurator } from "./setup/PluginConfigurator"; - -export namespace TypiaSetupWizard { - export interface IArguments { - manager: "npm" | "pnpm" | "yarn" | "bun"; - project: string | null; - } - - export async function setup(): Promise { - console.log("----------------------------------------"); - console.log(" Typia Setup Wizard"); - console.log("----------------------------------------"); - - // PREPARE ASSETS - const pack: PackageManager = await PackageManager.mount(); - const args: IArguments = await ArgumentParser.parse(pack)(inquiry); - - // INSTALL TYPESCRIPT COMPILERS - pack.install({ dev: true, modulo: "typescript", version: "5.5.2" }); - pack.install({ dev: true, modulo: "ts-patch", version: "latest" }); - args.project ??= (() => { - const runner: string = pack.manager === "npm" ? "npx" : pack.manager; - CommandExecutor.run(`${runner} tsc --init`); - return (args.project = "tsconfig.json"); - })(); - - // SETUP TRANSFORMER - await pack.save((data) => { - // COMPOSE PREPARE COMMAND - data.scripts ??= {}; - if ( - typeof data.scripts.prepare === "string" && - data.scripts.prepare.trim().length - ) { - if ( - data.scripts.prepare.indexOf("ts-patch install") === -1 && - data.scripts.prepare.indexOf("typia patch") === -1 - ) - data.scripts.prepare = - "ts-patch install && typia patch && " + data.scripts.prepare; - else if (data.scripts.prepare.indexOf("ts-patch install") === -1) - data.scripts.prepare = "ts-patch install && " + data.scripts.prepare; - else if (data.scripts.prepare.indexOf("typia patch") === -1) - data.scripts.prepare = data.scripts.prepare.replace( - "ts-patch install", - "ts-patch install && typia patch", - ); - } else data.scripts.prepare = "ts-patch install && typia patch"; - - // FOR OLDER VERSIONS - if (typeof data.scripts.postinstall === "string") { - data.scripts.postinstall = data.scripts.postinstall - .split("&&") - .map((str) => str.trim()) - .filter((str) => str.indexOf("ts-patch install") === -1) - .join(" && "); - if (data.scripts.postinstall.length === 0) - delete data.scripts.postinstall; - } - }); - - // CONFIGURE TYPIA - await PluginConfigurator.configure(args); - CommandExecutor.run(`${pack.manager} run prepare`); - } - - const inquiry: ArgumentParser.Inquiry = async ( - pack, - command, - prompt, - action, - ) => { - // PREPARE ASSETS - command.option("--manager [manager", "package manager"); - command.option("--project [project]", "tsconfig.json file location"); - - // INTERNAL PROCEDURES - const questioned = { value: false }; - const select = - (name: string) => - (message: string) => - async ( - choices: Choice[], - filter?: (choice: string) => Choice, - ): Promise => { - questioned.value = true; - return ( - await prompt()({ - type: "list", - name: name, - message: message, - choices: choices, - ...(filter - ? { - filter, - } - : {}), - }) - )[name]; - }; - const configure = async (): Promise => { - const fileList: string[] = await ( - await fs.promises.readdir(process.cwd()) - ) - .filter( - (str) => - str.substring(0, 8) === "tsconfig" && - str.substring(str.length - 5) === ".json", - ) - .sort((x, y) => - x === "tsconfig.json" - ? -1 - : y === "tsconfig.json" - ? 1 - : x < y - ? -1 - : 1, - ); - if (fileList.length === 0) { - if (process.cwd() !== pack.directory) - throw new URIError(`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) => { - pack.manager = options.manager ??= await select("manager")( - "Package Manager", - )( - [ - "npm" as const, - "pnpm" as const, - "bun" as const, - "yarn (berry is not supported)" as "yarn", - ], - (value) => value.split(" ")[0] as "yarn", - ); - options.project ??= await configure(); - - if (questioned.value) console.log(""); - return options as IArguments; - }); - }; -} diff --git a/src/cli/setup/ArgumentParser.ts b/src/cli/setup/ArgumentParser.ts deleted file mode 100644 index f692ab7b32..0000000000 --- a/src/cli/setup/ArgumentParser.ts +++ /dev/null @@ -1,43 +0,0 @@ -import commander from "commander"; -import inquirer from "inquirer"; - -import { PackageManager } from "./PackageManager"; - -export namespace ArgumentParser { - export type Inquiry = ( - pack: PackageManager, - command: commander.Command, - prompt: (opt?: inquirer.StreamOptions) => inquirer.PromptModule, - action: (closure: (options: Partial) => Promise) => Promise, - ) => Promise; - - export const parse = - (pack: PackageManager) => - async ( - inquiry: ( - pack: PackageManager, - command: commander.Command, - prompt: (opt?: inquirer.StreamOptions) => inquirer.PromptModule, - action: (closure: (options: Partial) => Promise) => Promise, - ) => Promise, - ): Promise => { - // TAKE OPTIONS - const action = (closure: (options: Partial) => Promise) => - new Promise((resolve, reject) => { - commander.program.action(async (options) => { - try { - resolve(await closure(options)); - } catch (exp) { - reject(exp); - } - }); - commander.program.parseAsync().catch(reject); - }); - return inquiry( - pack, - commander.program, - inquirer.createPromptModule, - action, - ); - }; -} diff --git a/src/cli/setup/CommandExecutor.ts b/src/cli/setup/CommandExecutor.ts deleted file mode 100644 index 59e7bd98ae..0000000000 --- a/src/cli/setup/CommandExecutor.ts +++ /dev/null @@ -1,8 +0,0 @@ -import cp from "child_process"; - -export namespace CommandExecutor { - export const run = (str: string): void => { - console.log(`\n$ ${str}`); - cp.execSync(str, { stdio: "inherit" }); - }; -} diff --git a/src/cli/setup/FileRetriever.ts b/src/cli/setup/FileRetriever.ts deleted file mode 100644 index 35a5ab27b7..0000000000 --- a/src/cli/setup/FileRetriever.ts +++ /dev/null @@ -1,22 +0,0 @@ -import fs from "fs"; -import path from "path"; - -export namespace FileRetriever { - export const directory = - (name: string) => - (dir: string, depth: number = 0): string | null => { - const location: string = path.join(dir, name); - if (fs.existsSync(location)) return dir; - else if (depth > 2) return null; - return directory(name)(path.join(dir, ".."), depth + 1); - }; - - export const file = - (name: string) => - (directory: string, depth: number = 0): string | null => { - const location: string = path.join(directory, name); - if (fs.existsSync(location)) return location; - else if (depth > 2) return null; - return file(name)(path.join(directory, ".."), depth + 1); - }; -} diff --git a/src/cli/setup/PackageManager.ts b/src/cli/setup/PackageManager.ts deleted file mode 100644 index 4963c6f5e4..0000000000 --- a/src/cli/setup/PackageManager.ts +++ /dev/null @@ -1,86 +0,0 @@ -import fs from "fs"; -import path from "path"; - -import { CommandExecutor } from "./CommandExecutor"; -import { FileRetriever } from "./FileRetriever"; - -const managers = ["npm", "pnpm", "yarn", "bun"] as const; -type Manager = (typeof managers)[number]; - -const installCmdTable = { - npm: "install", - pnpm: "add", - yarn: "add", - bun: "add", -} as const satisfies Record; -const devOptionTable = { - npm: "--save-dev", - pnpm: "--save-dev", - yarn: "--dev", - bun: "--dev", -} as const satisfies Record; - -export class PackageManager { - public manager: Manager = "npm"; - public get file(): string { - return path.join(this.directory, "package.json"); - } - - public static async mount(): Promise { - const location: string | null = await FileRetriever.directory( - "package.json", - )(process.cwd()); - if (location === null) - throw new URIError(`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; - modulo: string; - version: string; - }): boolean { - const cmd = installCmdTable[this.manager]; - const option = props.dev ? devOptionTable[this.manager] : ""; - const middle: string = `${cmd} ${option}` as const; - CommandExecutor.run( - `${this.manager} ${middle} ${props.modulo}${ - props.version ? `@${props.version}` : "" - }`, - ); - return true; - } - - 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; - } -} diff --git a/src/cli/setup/PluginConfigurator.ts b/src/cli/setup/PluginConfigurator.ts deleted file mode 100644 index a1e80af8e1..0000000000 --- a/src/cli/setup/PluginConfigurator.ts +++ /dev/null @@ -1,69 +0,0 @@ -import comments from "comment-json"; -import fs from "fs"; - -import { TypiaSetupWizard } from "../TypiaSetupWizard"; - -export namespace PluginConfigurator { - export async function configure( - args: TypiaSetupWizard.IArguments, - ): Promise { - // GET COMPILER-OPTIONS - const config: comments.CommentObject = comments.parse( - await fs.promises.readFile(args.project!, "utf8"), - ) as comments.CommentObject; - const compilerOptions = config.compilerOptions as - | comments.CommentObject - | undefined; - if (compilerOptions === undefined) - throw new ReferenceError( - `${args.project} file does not have "compilerOptions" property.`, - ); - - // PREPARE PLUGINS - const plugins: comments.CommentArray = (() => { - const plugins = compilerOptions.plugins as - | comments.CommentArray - | undefined; - if (plugins === undefined) return (compilerOptions.plugins = [] as any); - else if (!Array.isArray(plugins)) - throw new TypeError( - `"plugins" property of ${args.project} must be array type.`, - ); - return plugins; - })(); - - const strict: boolean | undefined = compilerOptions.strict as - | boolean - | undefined; - const strictNullChecks: boolean | undefined = - compilerOptions.strictNullChecks as boolean | undefined; - const oldbie: comments.CommentObject | undefined = plugins.find( - (p) => - typeof p === "object" && - p !== null && - p.transform === "typia/lib/transform", - ); - if ( - strictNullChecks !== false && - (strict === true || strictNullChecks === true) && - oldbie !== undefined - ) - return; - - // DO CONFIGURE - compilerOptions.strictNullChecks = true; - if (strict === undefined && strictNullChecks === undefined) - compilerOptions.strict = true; - if (oldbie === undefined) - plugins.push( - comments.parse(` - { - "transform": "typia/lib/transform" - }`) as comments.CommentObject, - ); - await fs.promises.writeFile( - args.project!, - comments.stringify(config, null, 2), - ); - } -} diff --git a/src/cli/typia.ts b/src/cli/typia.ts deleted file mode 100644 index bcdde5793a..0000000000 --- a/src/cli/typia.ts +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env node -const USAGE = `Wrong command has been detected. Use like below: - - npx typia setup \\ - --manager (npm|pnpm|yarn) \\ - --project {tsconfig.json file path} - - - npx typia setup - - npx typia setup --manager pnpm - - npx typia setup --project tsconfig.test.json - - npx typia generate - --input {directory} \\ - --output {directory} - - --npx typia generate --input src/templates --output src/functinoal -`; - -const halt = (desc: string): never => { - console.error(desc); - process.exit(-1); -}; - -const main = async (): Promise => { - try { - await import("comment-json"); - await import("inquirer"); - await import("commander"); - } catch { - halt(`typia has not been installed. Run "npm i typia" before.`); - } - - const type: string | undefined = process.argv[2]; - if (type === "setup") { - const { TypiaSetupWizard } = await import("./TypiaSetupWizard"); - await TypiaSetupWizard.setup(); - } else if (type === "patch") { - const { TypiaPatchWizard } = await import("./TypiaPatchWizard"); - await TypiaPatchWizard.main(); - } else if (type === "generate") { - try { - await import("typescript"); - } catch { - halt( - `typescript has not been installed. Run "npm i -D typescript" before.`, - ); - } - const { TypiaGenerateWizard } = await import("./TypiaGenerateWizard"); - await TypiaGenerateWizard.generate(); - } else halt(USAGE); -}; -main().catch((exp) => { - console.error(exp); - process.exit(-1); -}); From 940c5cf4f801613522a3eef004f8a4824228abed Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:47:59 +0100 Subject: [PATCH 15/21] feat(cli): add setup command --- src/cli/index.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/cli/index.ts b/src/cli/index.ts index 9efaac2d35..d77f1d3dcf 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -2,8 +2,10 @@ import { cli as cleye } from 'cleye' import { patch } from './subcommands/patch'; import { generate } from './subcommands/generate'; import { setup } from './subcommands/setup'; +import { wizard } from './utils/message'; export async function cli(){ + wizard(); const argv = cleye({ name: "typia", version: "1.0.0", @@ -17,7 +19,6 @@ export async function cli(){ }) - const { flags } = argv; - - console.log(argv) + /* if no subcommand is provided, show help */ + argv.showHelp(); } From d7580a4fc4e74ecd37f8bec0c1f63f11f5470fa8 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 28 Aug 2024 08:56:55 +0100 Subject: [PATCH 16/21] chore(package.json): revert deleted private --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index a7cb29afb1..c50087833a 100644 --- a/package.json +++ b/package.json @@ -111,5 +111,6 @@ "lib", "bin", "src" - ] + ], + "private": false } From 40f22e273154e244d79ae01722416054b12664df Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 28 Aug 2024 08:58:49 +0100 Subject: [PATCH 17/21] chore(package.json): update dependencies --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index c50087833a..46b860dc87 100644 --- a/package.json +++ b/package.json @@ -71,10 +71,8 @@ "dependencies": { "@samchon/openapi": "^0.4.9", "cleye": "^1.3.2", - "commander": "^10.0.0", "comment-json": "^4.2.3", "consola": "^3.2.3", - "inquirer": "^8.2.5", "package-manager-detector": "^0.2.0", "randexp": "^0.5.3", "tinyglobby": "^0.2.5" From 890e708847d632bc86d64ff8552ae4d464dc5885 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 28 Aug 2024 09:46:31 +0100 Subject: [PATCH 18/21] refactor(cli): use namespace exports for subcommand --- src/cli/index.ts | 10 ++++------ src/cli/subcommands/index.ts | 4 ++++ 2 files changed, 8 insertions(+), 6 deletions(-) create mode 100644 src/cli/subcommands/index.ts diff --git a/src/cli/index.ts b/src/cli/index.ts index d77f1d3dcf..761e47512d 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -1,7 +1,5 @@ import { cli as cleye } from 'cleye' -import { patch } from './subcommands/patch'; -import { generate } from './subcommands/generate'; -import { setup } from './subcommands/setup'; +import * as Subcommand from './subcommands' import { wizard } from './utils/message'; export async function cli(){ @@ -12,9 +10,9 @@ export async function cli(){ description: "CLI for Typia operations", commands: [ - patch, - generate, - setup, + Subcommand.patch, + Subcommand.generate, + Subcommand.setup, ], }) diff --git a/src/cli/subcommands/index.ts b/src/cli/subcommands/index.ts new file mode 100644 index 0000000000..90e18fb72c --- /dev/null +++ b/src/cli/subcommands/index.ts @@ -0,0 +1,4 @@ +export { patch } from "./patch"; +export { setup } from "./setup"; +export { generate } from "./generate"; + From 397ef22df35a5b872d79781eb98f5731a842e52f Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 28 Aug 2024 09:53:57 +0100 Subject: [PATCH 19/21] refactor(cli): use namespace exports for utility --- src/cli/subcommands/generate.ts | 14 +++++++------- src/cli/subcommands/patch.ts | 6 +++--- src/cli/subcommands/setup.ts | 34 ++++++++++++++++----------------- src/cli/utils/confFiles.ts | 8 ++++---- src/cli/utils/message.ts | 6 +++--- 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/cli/subcommands/generate.ts b/src/cli/subcommands/generate.ts index 448e3a70f7..d73c6ce111 100644 --- a/src/cli/subcommands/generate.ts +++ b/src/cli/subcommands/generate.ts @@ -1,9 +1,9 @@ import { command } from 'cleye'; import { TypiaProgrammer } from "../../programmers/TypiaProgrammer"; -import { findTsConfig } from "../utils/confFiles"; -import { logger } from "../utils/logger"; -import { bail } from "../utils/message"; +import * as ConfFileUtils from "../utils/confFiles"; +import * as Logger from "../utils/logger"; +import * as MessageUtils from "../utils/message"; export const generate = command({ name: "generate", @@ -29,12 +29,12 @@ export const generate = command({ }, async (argv) => { let { input, output, project } = argv.flags; - input ??= await logger.prompt("input directory", { type: "text" }); - output ??= await logger.prompt("output directory", { type: "text" }); - project ??= await findTsConfig(); + input ??= await Logger.logger.prompt("input directory", { type: "text" }); + output ??= await Logger.logger.prompt("output directory", { type: "text" }); + project ??= await ConfFileUtils.findTsConfig(); if (project == null) { - bail("tsconfig.json not found"); + MessageUtils.bail("tsconfig.json not found"); } await TypiaProgrammer.build({ input, output, project }); diff --git a/src/cli/subcommands/patch.ts b/src/cli/subcommands/patch.ts index a2eb50296d..6e73037861 100644 --- a/src/cli/subcommands/patch.ts +++ b/src/cli/subcommands/patch.ts @@ -1,7 +1,7 @@ import { command } from 'cleye'; import fs from "node:fs/promises"; -import { logger } from "../utils/logger"; +import * as Logger from "../utils/logger"; const FROM_WITH_COMMENT = `var defaultJSDocParsingMode = 2 /* ParseForTypeErrors */`; const TO_WITH_COMMENT = `var defaultJSDocParsingMode = 0 /* ParseAll */`; @@ -17,7 +17,7 @@ export const patch = command({ description: "Extra patching for TypeScript", } }, async () => { - logger.info( + Logger.logger.info( [ `Since TypeScript v5.3 update, "tsc" no more parses JSDoc comments.`, ``, @@ -28,7 +28,7 @@ export const patch = command({ ); await executePatch(); - logger.success("Patched TypeScript"); + Logger.logger.success("Patched TypeScript"); } ); diff --git a/src/cli/subcommands/setup.ts b/src/cli/subcommands/setup.ts index 0fc0016a2f..25a1631f56 100644 --- a/src/cli/subcommands/setup.ts +++ b/src/cli/subcommands/setup.ts @@ -6,11 +6,11 @@ import { detect } from "package-manager-detector"; import { AGENTS } from "package-manager-detector/constants"; import { resolveCommand } from 'package-manager-detector/commands' -import { run } from "../utils/command"; -import { findTsConfig } from "../utils/confFiles"; -import { findUp, readJsonFile, writeJsonFile } from "../utils/fs"; -import { logger } from "../utils/logger"; -import { bail } from "../utils/message"; +import * as CommandExecutor from "../utils/command"; +import * as ConfFileUtils from "../utils/confFiles"; +import * as FsUtils from "../utils/fs"; +import * as Logger from "../utils/logger"; +import * as MessageUtils from "../utils/message"; const TSPATCH_COMMAND = `ts-patch install`; const TYPIA_PATCH_COMMAND = `typia patch`; @@ -59,7 +59,7 @@ export const setup = command({ let agent = manager?.agent; if (agent == null) { - const selected = await logger.prompt("Select a package manager", { + const selected = await Logger.logger.prompt("Select a package manager", { initial: "npm", options: AGENTS, }) as typeof AGENTS[number]; @@ -68,7 +68,7 @@ export const setup = command({ /* yarn@berry is not supported */ if (agent === "yarn@berry") { - bail("yarn@berry is not supported."); + MessageUtils.bail("yarn@berry is not supported."); } /* install dependencies */ @@ -79,16 +79,16 @@ export const setup = command({ (agent==='pnpm' || agent==='pnpm@6') && existsSync(resolve(cwd, 'pnpm-workspace.yaml')) ? '-w' : '' ] const { command, args } = resolveCommand(agent, 'add', addArgs)!; - run(`${command} ${args.join(" ")}`); + CommandExecutor.run(`${command} ${args.join(" ")}`); } /* === prepare package.json === */ { - const path = await findUp("package.json", { cwd }); + const path = await FsUtils.findUp("package.json", { cwd }); if (path == null) { - bail("package.json not found."); + MessageUtils.bail("package.json not found."); } - const json = await readJsonFile(path, cwd); + const json = await FsUtils.readJsonFile(path, cwd); let prepare = ( (json.data?.scripts?.prepare as string | undefined) ?? "" @@ -111,19 +111,19 @@ export const setup = command({ /* update prepare script */ json.data.scripts = { ...(json.data.scripts ?? {}), prepare }; - await writeJsonFile(json); + await FsUtils.writeJsonFile(json); } /* === prepare tsconfig.json === */ { - const tsConfigPath = flags.project ?? (await findTsConfig({ cwd })); + const tsConfigPath = flags.project ?? (await ConfFileUtils.findTsConfig({ cwd })); /* if tsconfig.json is not found, create it */ if (tsConfigPath == null) { const { command, args } = resolveCommand(agent, 'execute', ['tsc --init'])!; - run(`${command} ${args.join(" ")}`); + CommandExecutor.run(`${command} ${args.join(" ")}`); } - const tsConfig = await readJsonFile(tsConfigPath, cwd); + const tsConfig = await FsUtils.readJsonFile(tsConfigPath, cwd); if (tsConfig.data.compilerOptions == null) { tsConfig.data.compilerOptions = {}; @@ -136,12 +136,12 @@ export const setup = command({ { transform: TYPIA_TRANSFORM }, ...(tsConfig.data.compilerOptions.plugins ?? []), ]; - await writeJsonFile(tsConfig); + await FsUtils.writeJsonFile(tsConfig); } /* === run prepare script === */ const { command, args } = resolveCommand(agent, 'run', ['prepare'])!; - run(`${command} ${args.join(" ")}`); + CommandExecutor.run(`${command} ${args.join(" ")}`); }, ); diff --git a/src/cli/utils/confFiles.ts b/src/cli/utils/confFiles.ts index 2e2270aa94..23604c8a88 100644 --- a/src/cli/utils/confFiles.ts +++ b/src/cli/utils/confFiles.ts @@ -1,8 +1,8 @@ import process from "node:process"; import { glob } from "tinyglobby"; -import { logger } from "./logger"; -import { bail } from "./message"; +import * as Logger from "../utils/logger"; +import * as MessageUtils from "../utils/message"; export async function findTsConfig( { cwd }: { cwd: string } = { cwd: process.cwd() }, @@ -10,7 +10,7 @@ export async function findTsConfig( const tsConfigs = await glob(["tsconfig.json", "tsconfig.*.json"], { cwd }); if (tsConfigs.length === 0) { - bail("tsconfig.json not found"); + MessageUtils.bail("tsconfig.json not found"); } if (tsConfigs.length === 1) { @@ -20,7 +20,7 @@ export async function findTsConfig( } } - return await logger.prompt( + return await Logger.logger.prompt( "Multiple tsconfig.json files found. Please specify the one to use:", { type: "select", diff --git a/src/cli/utils/message.ts b/src/cli/utils/message.ts index 85b9db4598..f0461fae7f 100644 --- a/src/cli/utils/message.ts +++ b/src/cli/utils/message.ts @@ -1,13 +1,13 @@ -import { logger } from "./logger"; +import * as Logger from "../utils/logger"; /** * throw an error message and exit the process */ export function bail(message: string): never { - logger.error(message); + Logger.logger.error(message); process.exit(1); } export function wizard(): void { - logger.box("Typia Setup Wizard"); + Logger.logger.box("Typia Setup Wizard"); } From 2684e8173ea5ff6b9abec777085b61f3cdea4ef8 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 28 Aug 2024 09:59:11 +0100 Subject: [PATCH 20/21] refactor(cli): re-export modules of `package-manager-detector` and import using namespace imports --- src/cli/subcommands/setup.ts | 16 +++++++--------- src/cli/utils/packageManager.ts | 8 ++++++++ 2 files changed, 15 insertions(+), 9 deletions(-) create mode 100644 src/cli/utils/packageManager.ts diff --git a/src/cli/subcommands/setup.ts b/src/cli/subcommands/setup.ts index 25a1631f56..e6a72edd9d 100644 --- a/src/cli/subcommands/setup.ts +++ b/src/cli/subcommands/setup.ts @@ -2,10 +2,8 @@ import { command } from 'cleye'; import process from "node:process"; import { existsSync } from 'node:fs'; import { resolve } from 'node:path'; -import { detect } from "package-manager-detector"; -import { AGENTS } from "package-manager-detector/constants"; -import { resolveCommand } from 'package-manager-detector/commands' +import * as PackageManager from '../utils/packageManager'; import * as CommandExecutor from "../utils/command"; import * as ConfFileUtils from "../utils/confFiles"; import * as FsUtils from "../utils/fs"; @@ -55,14 +53,14 @@ export const setup = command({ }, async (argv) => { const { flags } = argv; const cwd = process.cwd(); - const manager = await detect({ cwd }); + const manager = await PackageManager.detect({ cwd }); let agent = manager?.agent; if (agent == null) { const selected = await Logger.logger.prompt("Select a package manager", { initial: "npm", - options: AGENTS, - }) as typeof AGENTS[number]; + options: PackageManager.AGENTS, + }) as PackageManager.Agent; agent = selected; } @@ -78,7 +76,7 @@ export const setup = command({ dep.dev ? "-D" : "", (agent==='pnpm' || agent==='pnpm@6') && existsSync(resolve(cwd, 'pnpm-workspace.yaml')) ? '-w' : '' ] - const { command, args } = resolveCommand(agent, 'add', addArgs)!; + const { command, args } = PackageManager.resolveCommand(agent, 'add', addArgs)!; CommandExecutor.run(`${command} ${args.join(" ")}`); } @@ -119,7 +117,7 @@ export const setup = command({ const tsConfigPath = flags.project ?? (await ConfFileUtils.findTsConfig({ cwd })); /* if tsconfig.json is not found, create it */ if (tsConfigPath == null) { - const { command, args } = resolveCommand(agent, 'execute', ['tsc --init'])!; + const { command, args } = PackageManager.resolveCommand(agent, 'execute', ['tsc --init'])!; CommandExecutor.run(`${command} ${args.join(" ")}`); } @@ -140,7 +138,7 @@ export const setup = command({ } /* === run prepare script === */ - const { command, args } = resolveCommand(agent, 'run', ['prepare'])!; + const { command, args } = PackageManager.resolveCommand(agent, 'run', ['prepare'])!; CommandExecutor.run(`${command} ${args.join(" ")}`); }, ); diff --git a/src/cli/utils/packageManager.ts b/src/cli/utils/packageManager.ts new file mode 100644 index 0000000000..1b656a725e --- /dev/null +++ b/src/cli/utils/packageManager.ts @@ -0,0 +1,8 @@ +import { detect } from "package-manager-detector"; +import { AGENTS } from "package-manager-detector/constants"; +import { resolveCommand } from 'package-manager-detector/commands' + +type Agent = typeof AGENTS[number]; + +export { detect, AGENTS, resolveCommand }; +export type { Agent }; From 72ffc866e9bd6234d334090ff5daa876cd83b465 Mon Sep 17 00:00:00 2001 From: Jeongho Nam Date: Tue, 10 Sep 2024 01:25:00 +0900 Subject: [PATCH 21/21] Complement #1245: keep only `package-manager-detector`. Due to the previous PR #1245 had changed too much things, I could not follow it up. Instead, I've accepted its key feature, package manager detecting. --- benchmark/package.json | 2 +- errors/package.json | 2 +- package.json | 19 +-- packages/typescript-json/package.json | 4 +- packages/typescript-json/tsconfig.json | 3 +- src/cli/index.ts | 22 --- src/cli/subcommands/generate.ts | 42 ------ src/cli/subcommands/index.ts | 4 - src/cli/subcommands/patch.ts | 47 ------ src/cli/subcommands/setup.ts | 150 ------------------- src/cli/utils/command.ts | 6 - src/cli/utils/confFiles.ts | 30 ---- src/cli/utils/fs.ts | 105 -------------- src/cli/utils/logger.ts | 3 - src/cli/utils/message.ts | 13 -- src/cli/utils/packageManager.ts | 8 -- src/executable/TypiaGenerateWizard.ts | 83 +++++++++++ src/executable/TypiaPatchWizard.ts | 42 ++++++ src/executable/TypiaSetupWizard.ts | 160 +++++++++++++++++++++ src/executable/setup/ArgumentParser.ts | 43 ++++++ src/executable/setup/CommandExecutor.ts | 8 ++ src/executable/setup/FileRetriever.ts | 22 +++ src/executable/setup/PackageManager.ts | 86 +++++++++++ src/executable/setup/PluginConfigurator.ts | 69 +++++++++ src/executable/typia.ts | 55 +++++++ test-esm/package.json | 2 +- test/package.json | 2 +- 27 files changed, 583 insertions(+), 449 deletions(-) delete mode 100644 src/cli/index.ts delete mode 100644 src/cli/subcommands/generate.ts delete mode 100644 src/cli/subcommands/index.ts delete mode 100644 src/cli/subcommands/patch.ts delete mode 100644 src/cli/subcommands/setup.ts delete mode 100644 src/cli/utils/command.ts delete mode 100644 src/cli/utils/confFiles.ts delete mode 100644 src/cli/utils/fs.ts delete mode 100644 src/cli/utils/logger.ts delete mode 100644 src/cli/utils/message.ts delete mode 100644 src/cli/utils/packageManager.ts create mode 100644 src/executable/TypiaGenerateWizard.ts create mode 100644 src/executable/TypiaPatchWizard.ts create mode 100644 src/executable/TypiaSetupWizard.ts create mode 100644 src/executable/setup/ArgumentParser.ts create mode 100644 src/executable/setup/CommandExecutor.ts create mode 100644 src/executable/setup/FileRetriever.ts create mode 100644 src/executable/setup/PackageManager.ts create mode 100644 src/executable/setup/PluginConfigurator.ts create mode 100644 src/executable/typia.ts diff --git a/benchmark/package.json b/benchmark/package.json index c9e0d17720..ac4433e060 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -72,6 +72,6 @@ "suppress-warnings": "^1.0.2", "tstl": "^3.0.0", "uuid": "^9.0.1", - "typia": "../typia-6.10.0-dev.20240910.tgz" + "typia": "../typia-6.10.0-dev.20240910-2.tgz" } } \ No newline at end of file diff --git a/errors/package.json b/errors/package.json index 1483e12a6a..d05d0f2f33 100644 --- a/errors/package.json +++ b/errors/package.json @@ -32,6 +32,6 @@ "typescript": "^5.3.2" }, "dependencies": { - "typia": "../typia-6.10.0-dev.20240910.tgz" + "typia": "../typia-6.10.0-dev.20240910-2.tgz" } } \ No newline at end of file diff --git a/package.json b/package.json index 0e84b1aff6..bfc672b0aa 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "typia", - "version": "6.10.0-dev.20240910", + "version": "6.10.0-dev.20240910-2", "description": "Superfast runtime validators with only one line", "main": "lib/index.js", "typings": "lib/index.d.ts", "module": "lib/index.mjs", "bin": { - "typia": "./bin/typia.mjs" + "typia": "./lib/executable/typia.js" }, "tsp": { "tscOptions": { @@ -18,10 +18,8 @@ "test:bun": "bun run deploy/bun.ts", "test:template": "npm run --tag test --template", "-------------------------------------------------": "", - "build": "rimraf lib && tsc && tsc -p ./tsconfig.cli.json && rollup -c", - "cli": "node ./bin/typia.mjs", + "build": "rimraf lib && tsc && rollup -c", "dev": "rimraf lib && tsc --watch", - "dev:cli": "ts-node ./src/index.ts", "eslint": "eslint ./**/*.ts", "eslint:fix": "eslint ./**/*.ts --fix", "prettier": "prettier src --write", @@ -70,13 +68,11 @@ "homepage": "https://typia.io", "dependencies": { "@samchon/openapi": "^1.0.0", - "cleye": "^1.3.2", "commander": "^10.0.0", "comment-json": "^4.2.3", - "consola": "^3.2.3", + "inquirer": "^8.2.5", "package-manager-detector": "^0.2.0", - "randexp": "^0.5.3", - "tinyglobby": "^0.2.5" + "randexp": "^0.5.3" }, "peerDependencies": { "typescript": ">=4.8.0 <5.6.0" @@ -108,8 +104,7 @@ "README.md", "package.json", "lib", - "bin", "src" ], - "private": false -} + "private": true +} \ No newline at end of file diff --git a/packages/typescript-json/package.json b/packages/typescript-json/package.json index f45f8f73b5..7ef36d9fcc 100644 --- a/packages/typescript-json/package.json +++ b/packages/typescript-json/package.json @@ -1,6 +1,6 @@ { "name": "typescript-json", - "version": "6.10.0-dev.20240910", + "version": "6.10.0-dev.20240910-2", "description": "Superfast runtime validators with only one line", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -63,7 +63,7 @@ }, "homepage": "https://typia.io", "dependencies": { - "typia": "6.10.0-dev.20240910" + "typia": "6.10.0-dev.20240910-2" }, "peerDependencies": { "typescript": ">=4.8.0 <5.6.0" diff --git a/packages/typescript-json/tsconfig.json b/packages/typescript-json/tsconfig.json index 2e9fb09c29..f3914ece9a 100644 --- a/packages/typescript-json/tsconfig.json +++ b/packages/typescript-json/tsconfig.json @@ -101,5 +101,6 @@ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, - "include": ["src"] + "include": ["src"], + "exclude": ["src/cli"] } diff --git a/src/cli/index.ts b/src/cli/index.ts deleted file mode 100644 index 761e47512d..0000000000 --- a/src/cli/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { cli as cleye } from 'cleye' -import * as Subcommand from './subcommands' -import { wizard } from './utils/message'; - -export async function cli(){ - wizard(); - const argv = cleye({ - name: "typia", - version: "1.0.0", - description: "CLI for Typia operations", - - commands: [ - Subcommand.patch, - Subcommand.generate, - Subcommand.setup, - ], - - }) - - /* if no subcommand is provided, show help */ - argv.showHelp(); -} diff --git a/src/cli/subcommands/generate.ts b/src/cli/subcommands/generate.ts deleted file mode 100644 index d73c6ce111..0000000000 --- a/src/cli/subcommands/generate.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { command } from 'cleye'; -import { TypiaProgrammer } from "../../programmers/TypiaProgrammer"; - -import * as ConfFileUtils from "../utils/confFiles"; -import * as Logger from "../utils/logger"; -import * as MessageUtils from "../utils/message"; - -export const generate = command({ - name: "generate", - - flags: { - input: { - type: String, - description: "input directory", - }, - output: { - type: String, - description: "output directory", - }, - project: { - type: String, - description: "tsconfig.json file path (e.g. ./tsconfig.test.json)", - }, - }, - - help: { - description: "Generate Typia files", - } -}, async (argv) => { - let { input, output, project } = argv.flags; - - input ??= await Logger.logger.prompt("input directory", { type: "text" }); - output ??= await Logger.logger.prompt("output directory", { type: "text" }); - project ??= await ConfFileUtils.findTsConfig(); - - if (project == null) { - MessageUtils.bail("tsconfig.json not found"); - } - - await TypiaProgrammer.build({ input, output, project }); - }, -); diff --git a/src/cli/subcommands/index.ts b/src/cli/subcommands/index.ts deleted file mode 100644 index 90e18fb72c..0000000000 --- a/src/cli/subcommands/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { patch } from "./patch"; -export { setup } from "./setup"; -export { generate } from "./generate"; - diff --git a/src/cli/subcommands/patch.ts b/src/cli/subcommands/patch.ts deleted file mode 100644 index 6e73037861..0000000000 --- a/src/cli/subcommands/patch.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { command } from 'cleye'; -import fs from "node:fs/promises"; - -import * as Logger from "../utils/logger"; - -const FROM_WITH_COMMENT = `var defaultJSDocParsingMode = 2 /* ParseForTypeErrors */`; -const TO_WITH_COMMENT = `var defaultJSDocParsingMode = 0 /* ParseAll */`; -const FROM_ONLY = `var defaultJSDocParsingMode = 2`; -const TO_ONLY = `var defaultJSDocParsingMode = 0`; - -export const patch = command({ - name: "patch", - - aliases: ["p"], - - help: { - description: "Extra patching for TypeScript", - } -}, async () => { - Logger.logger.info( - [ - `Since TypeScript v5.3 update, "tsc" no more parses JSDoc comments.`, - ``, - `Therefore, "typia" revives the JSDoc parsing feature by patching "tsc".`, - ``, - `This is a temporary feature of "typia", and it would be removed when "ts-patch" being updated.`, - ].join("\n"), - ); - - await executePatch(); - Logger.logger.success("Patched TypeScript"); - } -); - -export async function executePatch(): Promise { - const location: string = require.resolve("typescript/lib/tsc.js"); - const content: string = await fs.readFile(location, "utf8"); - if (!content.includes(FROM_WITH_COMMENT)) { - await fs.writeFile( - location, - content.replace(FROM_WITH_COMMENT, TO_WITH_COMMENT), - "utf8", - ); - } else if (!content.includes(FROM_ONLY)) { - await fs.writeFile(location, content.replace(FROM_ONLY, TO_ONLY), "utf8"); - } -} diff --git a/src/cli/subcommands/setup.ts b/src/cli/subcommands/setup.ts deleted file mode 100644 index e6a72edd9d..0000000000 --- a/src/cli/subcommands/setup.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { command } from 'cleye'; -import process from "node:process"; -import { existsSync } from 'node:fs'; -import { resolve } from 'node:path'; - -import * as PackageManager from '../utils/packageManager'; -import * as CommandExecutor from "../utils/command"; -import * as ConfFileUtils from "../utils/confFiles"; -import * as FsUtils from "../utils/fs"; -import * as Logger from "../utils/logger"; -import * as MessageUtils from "../utils/message"; - -const TSPATCH_COMMAND = `ts-patch install`; -const TYPIA_PATCH_COMMAND = `typia patch`; -const TYPIA_TRANSFORM = `typia/lib/transform`; - -/** package.json type */ -interface PackageJson { - scripts?: Record; -} - -/** tsconfig.json type */ -interface TSConfig { - compilerOptions?: { - strictNullChecks?: boolean; - strict?: boolean; - plugins?: { - transform: string; - }[]; - }; -} - -/** dependency type */ -interface Dependency { - dev: boolean; - modulo: string; - version: string; -} - -export const setup = command({ - name: "setup", - - flags: { - project: { - type: String, - description: "tsconfig.json file path (e.g. ./tsconfig.test.json)", - }, - }, - - help: { - description: "Setup Typia", - } -}, async (argv) => { - const { flags } = argv; - const cwd = process.cwd(); - const manager = await PackageManager.detect({ cwd }); - let agent = manager?.agent; - - if (agent == null) { - const selected = await Logger.logger.prompt("Select a package manager", { - initial: "npm", - options: PackageManager.AGENTS, - }) as PackageManager.Agent; - agent = selected; - } - - /* yarn@berry is not supported */ - if (agent === "yarn@berry") { - MessageUtils.bail("yarn@berry is not supported."); - } - - /* install dependencies */ - for (const dep of DEPENDENCIES) { - const addArgs= [ - `${dep.modulo}@${dep.version}`, - dep.dev ? "-D" : "", - (agent==='pnpm' || agent==='pnpm@6') && existsSync(resolve(cwd, 'pnpm-workspace.yaml')) ? '-w' : '' - ] - const { command, args } = PackageManager.resolveCommand(agent, 'add', addArgs)!; - CommandExecutor.run(`${command} ${args.join(" ")}`); - } - - /* === prepare package.json === */ - { - const path = await FsUtils.findUp("package.json", { cwd }); - if (path == null) { - MessageUtils.bail("package.json not found."); - } - const json = await FsUtils.readJsonFile(path, cwd); - - let prepare = ( - (json.data?.scripts?.prepare as string | undefined) ?? "" - ).trim(); - - const FULL_COMMAND = `${TSPATCH_COMMAND} && ${TYPIA_PATCH_COMMAND}`; - - /* if ony `ts-patch install` is found, add `typia patch` */ - prepare.replace(TSPATCH_COMMAND, FULL_COMMAND); - - /* if prepare script is empty, set it to `typia patch` */ - if (prepare === "") { - prepare = FULL_COMMAND; - } - - /* if prepare script does not contain `typia patch`, add it */ - if (prepare !== FULL_COMMAND && !prepare.includes(FULL_COMMAND)) { - prepare = `${FULL_COMMAND} && ${prepare}`; - } - - /* update prepare script */ - json.data.scripts = { ...(json.data.scripts ?? {}), prepare }; - await FsUtils.writeJsonFile(json); - } - - /* === prepare tsconfig.json === */ - { - const tsConfigPath = flags.project ?? (await ConfFileUtils.findTsConfig({ cwd })); - /* if tsconfig.json is not found, create it */ - if (tsConfigPath == null) { - const { command, args } = PackageManager.resolveCommand(agent, 'execute', ['tsc --init'])!; - CommandExecutor.run(`${command} ${args.join(" ")}`); - } - - const tsConfig = await FsUtils.readJsonFile(tsConfigPath, cwd); - - if (tsConfig.data.compilerOptions == null) { - tsConfig.data.compilerOptions = {}; - } - - tsConfig.data.compilerOptions.strictNullChecks = true; - tsConfig.data.compilerOptions.strict = true; - - tsConfig.data.compilerOptions.plugins = [ - { transform: TYPIA_TRANSFORM }, - ...(tsConfig.data.compilerOptions.plugins ?? []), - ]; - await FsUtils.writeJsonFile(tsConfig); - } - - /* === run prepare script === */ - const { command, args } = PackageManager.resolveCommand(agent, 'run', ['prepare'])!; - CommandExecutor.run(`${command} ${args.join(" ")}`); - }, -); - -/** dependencies to be installed */ -const DEPENDENCIES = [ - { dev: true, modulo: "typescript", version: "5.5.2" }, - { dev: true, modulo: "ts-patch", version: "latest" }, -] as const satisfies Dependency[]; diff --git a/src/cli/utils/command.ts b/src/cli/utils/command.ts deleted file mode 100644 index 7abfb29ead..0000000000 --- a/src/cli/utils/command.ts +++ /dev/null @@ -1,6 +0,0 @@ -import cp from "child_process"; - -export function run(str: string): void { - console.log(`\n$ ${str}`); - cp.execSync(str, { stdio: "inherit" }); -} diff --git a/src/cli/utils/confFiles.ts b/src/cli/utils/confFiles.ts deleted file mode 100644 index 23604c8a88..0000000000 --- a/src/cli/utils/confFiles.ts +++ /dev/null @@ -1,30 +0,0 @@ -import process from "node:process"; -import { glob } from "tinyglobby"; - -import * as Logger from "../utils/logger"; -import * as MessageUtils from "../utils/message"; - -export async function findTsConfig( - { cwd }: { cwd: string } = { cwd: process.cwd() }, -): Promise { - const tsConfigs = await glob(["tsconfig.json", "tsconfig.*.json"], { cwd }); - - if (tsConfigs.length === 0) { - MessageUtils.bail("tsconfig.json not found"); - } - - if (tsConfigs.length === 1) { - const tsconfig = tsConfigs.at(0); - if (tsconfig != null) { - return tsconfig; - } - } - - return await Logger.logger.prompt( - "Multiple tsconfig.json files found. Please specify the one to use:", - { - type: "select", - options: tsConfigs, - }, - ); -} diff --git a/src/cli/utils/fs.ts b/src/cli/utils/fs.ts deleted file mode 100644 index f9b85cb9c6..0000000000 --- a/src/cli/utils/fs.ts +++ /dev/null @@ -1,105 +0,0 @@ -// @see https://github.com/ryoppippi/bumpp/blob/e93efe88bba42bd0875f12f1c10744f41b732b6e/src/fs.ts -import * as cj from "comment-json"; -import fs from "node:fs"; -import fsPromises from "node:fs/promises"; -import path from "node:path"; -import process from "node:process"; - -/** - * Find a file in the directory hierarchy - */ -export async function findUp( - name: string | string[], - { cwd }: { cwd: string | undefined } = { cwd: process.cwd() }, -): Promise { - let directory = path.resolve(cwd ?? process.cwd()); - const { root } = path.parse(directory); - const names = [name].flat(); - - while (directory && directory !== root) { - for (const name of names) { - const filePath = path.join(directory, name); - - try { - const stats = await fsPromises.stat(filePath); - if (stats.isFile()) { - return filePath; - } - } catch {} - } - - directory = path.dirname(directory); - } - return; -} - -/** - * Describes a plain-text file. - */ -export interface TextFile { - path: string; - data: string; -} - -/** - * Describes a JSON file. - */ -interface JsonFile { - path: Readonly; - data: T & cj.CommentJSONValue; -} - -/** - * Reads a JSON file and returns the parsed data. - * This functions supports JSON/JSONC/JSON with comments. - */ -export async function readJsonFile( - name: string, - cwd: string, -): Promise> { - const file = await readTextFile(name, cwd); - const data = cj.parse(file.data) as T & cj.CommentObject; - - return { ...file, data }; -} - -/** - * Writes the given data to the specified JSON/JSONC file. - */ -export async function writeJsonFile(file: JsonFile): Promise { - const newJSON = cj.stringify(file.data, null, 2); - - return writeTextFile({ ...file, data: newJSON }); -} - -/** - * Reads a text file and returns its contents. - */ -export function readTextFile(name: string, cwd: string): Promise { - return new Promise((resolve, reject) => { - const filePath = path.isAbsolute(name) ? name : path.resolve(cwd, name); - - fs.readFile(filePath, "utf8", (err, text) => { - if (err) { - reject(err); - } else { - resolve({ - path: filePath, - data: text, - }); - } - }); - }); -} - -/** - * Writes the given text to the specified file. - */ -export function writeTextFile(file: TextFile): Promise { - return new Promise((resolve, reject) => { - fs.writeFile(file.path, file.data, (err) => { - if (err) reject(err); - else resolve(); - }); - }); -} diff --git a/src/cli/utils/logger.ts b/src/cli/utils/logger.ts deleted file mode 100644 index 2c5be55c76..0000000000 --- a/src/cli/utils/logger.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { type ConsolaInstance, consola } from "consola"; - -export const logger: ConsolaInstance = consola.withTag("typia-cli"); diff --git a/src/cli/utils/message.ts b/src/cli/utils/message.ts deleted file mode 100644 index f0461fae7f..0000000000 --- a/src/cli/utils/message.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as Logger from "../utils/logger"; - -/** - * throw an error message and exit the process - */ -export function bail(message: string): never { - Logger.logger.error(message); - process.exit(1); -} - -export function wizard(): void { - Logger.logger.box("Typia Setup Wizard"); -} diff --git a/src/cli/utils/packageManager.ts b/src/cli/utils/packageManager.ts deleted file mode 100644 index 1b656a725e..0000000000 --- a/src/cli/utils/packageManager.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { detect } from "package-manager-detector"; -import { AGENTS } from "package-manager-detector/constants"; -import { resolveCommand } from 'package-manager-detector/commands' - -type Agent = typeof AGENTS[number]; - -export { detect, AGENTS, resolveCommand }; -export type { Agent }; diff --git a/src/executable/TypiaGenerateWizard.ts b/src/executable/TypiaGenerateWizard.ts new file mode 100644 index 0000000000..f3c5e55ac4 --- /dev/null +++ b/src/executable/TypiaGenerateWizard.ts @@ -0,0 +1,83 @@ +import fs from "fs"; + +import { TypiaProgrammer } from "../programmers/TypiaProgrammer"; + +import { ArgumentParser } from "./setup/ArgumentParser"; +import { PackageManager } from "./setup/PackageManager"; + +export namespace TypiaGenerateWizard { + export async function generate(): Promise { + console.log("----------------------------------------"); + console.log(" Typia Generate Wizard"); + console.log("----------------------------------------"); + + // LOAD PACKAGE.JSON INFO + const pack: PackageManager = await PackageManager.mount(); + const options: IArguments = await ArgumentParser.parse(pack)(inquiry); + await TypiaProgrammer.build(options); + } + + const inquiry: ArgumentParser.Inquiry = async ( + _pack, + command, + prompt, + action, + ) => { + // PREPARE ASSETS + command.option("--input [path]", "input directory"); + command.option("--output [directory]", "output directory"); + command.option("--project [project]", "tsconfig.json file location"); + + const questioned = { value: false }; + + const input = (name: string) => async (message: string) => { + const result = await prompt()({ + type: "input", + name, + message, + default: "", + }); + return result[name] as string; + }; + const select = + (name: string) => + (message: string) => + async (choices: Choice[]): Promise => { + questioned.value = true; + return ( + await prompt()({ + type: "list", + name: name, + message: message, + choices: choices, + }) + )[name]; + }; + const configure = async (): Promise => { + const files: string[] = await ( + await fs.promises.readdir(process.cwd()) + ).filter( + (str) => + str.substring(0, 8) === "tsconfig" && + str.substring(str.length - 5) === ".json", + ); + if (files.length === 0) + throw new URIError(`Unable to find "tsconfig.json" file.`); + else if (files.length === 1) return files[0]!; + return select("tsconfig")("TS Config File")(files); + }; + + return action(async (options) => { + options.input ??= await input("input")("input directory"); + options.output ??= await input("output")("output directory"); + options.project ??= await configure(); + return options as IArguments; + }); + }; + + export interface IArguments { + input: string; + output: string; + project: string; + } +} diff --git a/src/executable/TypiaPatchWizard.ts b/src/executable/TypiaPatchWizard.ts new file mode 100644 index 0000000000..ac31631c0a --- /dev/null +++ b/src/executable/TypiaPatchWizard.ts @@ -0,0 +1,42 @@ +import fs from "fs"; + +export namespace TypiaPatchWizard { + export const main = async (): Promise => { + console.log("----------------------------------------"); + console.log(" Typia Setup Wizard"); + console.log("----------------------------------------"); + console.log( + [ + `Since TypeScript v5.3 update, "tsc" no more parses JSDoc comments.`, + ``, + `Therefore, "typia" revives the JSDoc parsing feature by patching "tsc".`, + ``, + `This is a temporary feature of "typia", and it would be removed when "ts-patch" being updated.`, + ].join("\n"), + ); + + await patch(); + }; + + export const patch = async (): Promise => { + const location: string = require.resolve("typescript/lib/tsc.js"); + const content: string = await fs.promises.readFile(location, "utf8"); + if (content.indexOf(FROM_WITH_COMMENT) !== -1) + await fs.promises.writeFile( + location, + content.replace(FROM_WITH_COMMENT, TO_WITH_COMMENT), + "utf8", + ); + else if (content.indexOf(FROM_ONLY) !== -1) + await fs.promises.writeFile( + location, + content.replace(FROM_ONLY, TO_ONLY), + "utf8", + ); + }; +} + +const FROM_WITH_COMMENT = `var defaultJSDocParsingMode = 2 /* ParseForTypeErrors */`; +const TO_WITH_COMMENT = `var defaultJSDocParsingMode = 0 /* ParseAll */`; +const FROM_ONLY = `var defaultJSDocParsingMode = 2`; +const TO_ONLY = `var defaultJSDocParsingMode = 0`; diff --git a/src/executable/TypiaSetupWizard.ts b/src/executable/TypiaSetupWizard.ts new file mode 100644 index 0000000000..a1da1cb641 --- /dev/null +++ b/src/executable/TypiaSetupWizard.ts @@ -0,0 +1,160 @@ +import fs from "fs"; +import { DetectResult, detect } from "package-manager-detector"; + +import { ArgumentParser } from "./setup/ArgumentParser"; +import { CommandExecutor } from "./setup/CommandExecutor"; +import { PackageManager } from "./setup/PackageManager"; +import { PluginConfigurator } from "./setup/PluginConfigurator"; + +export namespace TypiaSetupWizard { + export interface IArguments { + manager: "npm" | "pnpm" | "yarn" | "bun"; + project: string | null; + } + + export async function setup(): Promise { + console.log("----------------------------------------"); + console.log(" Typia Setup Wizard"); + console.log("----------------------------------------"); + + // PREPARE ASSETS + const pack: PackageManager = await PackageManager.mount(); + const args: IArguments = await ArgumentParser.parse(pack)(inquiry); + + // INSTALL TYPESCRIPT COMPILERS + pack.install({ dev: true, modulo: "typescript", version: "5.5.2" }); + pack.install({ dev: true, modulo: "ts-patch", version: "latest" }); + args.project ??= (() => { + const runner: string = pack.manager === "npm" ? "npx" : pack.manager; + CommandExecutor.run(`${runner} tsc --init`); + return (args.project = "tsconfig.json"); + })(); + + // SETUP TRANSFORMER + await pack.save((data) => { + // COMPOSE PREPARE COMMAND + data.scripts ??= {}; + if ( + typeof data.scripts.prepare === "string" && + data.scripts.prepare.trim().length + ) { + if ( + data.scripts.prepare.indexOf("ts-patch install") === -1 && + data.scripts.prepare.indexOf("typia patch") === -1 + ) + data.scripts.prepare = + "ts-patch install && typia patch && " + data.scripts.prepare; + else if (data.scripts.prepare.indexOf("ts-patch install") === -1) + data.scripts.prepare = "ts-patch install && " + data.scripts.prepare; + else if (data.scripts.prepare.indexOf("typia patch") === -1) + data.scripts.prepare = data.scripts.prepare.replace( + "ts-patch install", + "ts-patch install && typia patch", + ); + } else data.scripts.prepare = "ts-patch install && typia patch"; + + // FOR OLDER VERSIONS + if (typeof data.scripts.postinstall === "string") { + data.scripts.postinstall = data.scripts.postinstall + .split("&&") + .map((str) => str.trim()) + .filter((str) => str.indexOf("ts-patch install") === -1) + .join(" && "); + if (data.scripts.postinstall.length === 0) + delete data.scripts.postinstall; + } + }); + + // CONFIGURE TYPIA + await PluginConfigurator.configure(args); + CommandExecutor.run(`${pack.manager} run prepare`); + } + + const inquiry: ArgumentParser.Inquiry = async ( + pack, + command, + prompt, + action, + ) => { + // PREPARE ASSETS + command.option("--manager [manager", "package manager"); + command.option("--project [project]", "tsconfig.json file location"); + + // INTERNAL PROCEDURES + const questioned = { value: false }; + const select = + (name: string) => + (message: string) => + async ( + choices: Choice[], + filter?: (choice: string) => Choice, + ): Promise => { + questioned.value = true; + return ( + await prompt()({ + type: "list", + name: name, + message: message, + choices: choices, + ...(filter + ? { + filter, + } + : {}), + }) + )[name]; + }; + const configure = async (): Promise => { + const fileList: string[] = await ( + await fs.promises.readdir(process.cwd()) + ) + .filter( + (str) => + str.substring(0, 8) === "tsconfig" && + str.substring(str.length - 5) === ".json", + ) + .sort((x, y) => + x === "tsconfig.json" + ? -1 + : y === "tsconfig.json" + ? 1 + : x < y + ? -1 + : 1, + ); + if (fileList.length === 0) { + if (process.cwd() !== pack.directory) + throw new URIError(`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) => { + pack.manager = options.manager ??= + (await detectManager()) ?? + (await select("manager")("Package Manager")( + [ + "npm" as const, + "pnpm" as const, + "bun" as const, + "yarn (berry is not supported)" as "yarn", + ], + (value) => value.split(" ")[0] as "yarn", + )); + options.project ??= await configure(); + + if (questioned.value) console.log(""); + return options as IArguments; + }); + }; + + const detectManager = async (): Promise< + "npm" | "pnpm" | "yarn" | "bun" | null + > => { + const result: DetectResult | null = await detect({ cwd: process.cwd() }); + if (result?.name === "npm") return null; // NPM case is still selectable + return result?.name ?? null; + }; +} diff --git a/src/executable/setup/ArgumentParser.ts b/src/executable/setup/ArgumentParser.ts new file mode 100644 index 0000000000..f692ab7b32 --- /dev/null +++ b/src/executable/setup/ArgumentParser.ts @@ -0,0 +1,43 @@ +import commander from "commander"; +import inquirer from "inquirer"; + +import { PackageManager } from "./PackageManager"; + +export namespace ArgumentParser { + export type Inquiry = ( + pack: PackageManager, + command: commander.Command, + prompt: (opt?: inquirer.StreamOptions) => inquirer.PromptModule, + action: (closure: (options: Partial) => Promise) => Promise, + ) => Promise; + + export const parse = + (pack: PackageManager) => + async ( + inquiry: ( + pack: PackageManager, + command: commander.Command, + prompt: (opt?: inquirer.StreamOptions) => inquirer.PromptModule, + action: (closure: (options: Partial) => Promise) => Promise, + ) => Promise, + ): Promise => { + // TAKE OPTIONS + const action = (closure: (options: Partial) => Promise) => + new Promise((resolve, reject) => { + commander.program.action(async (options) => { + try { + resolve(await closure(options)); + } catch (exp) { + reject(exp); + } + }); + commander.program.parseAsync().catch(reject); + }); + return inquiry( + pack, + commander.program, + inquirer.createPromptModule, + action, + ); + }; +} diff --git a/src/executable/setup/CommandExecutor.ts b/src/executable/setup/CommandExecutor.ts new file mode 100644 index 0000000000..59e7bd98ae --- /dev/null +++ b/src/executable/setup/CommandExecutor.ts @@ -0,0 +1,8 @@ +import cp from "child_process"; + +export namespace CommandExecutor { + export const run = (str: string): void => { + console.log(`\n$ ${str}`); + cp.execSync(str, { stdio: "inherit" }); + }; +} diff --git a/src/executable/setup/FileRetriever.ts b/src/executable/setup/FileRetriever.ts new file mode 100644 index 0000000000..35a5ab27b7 --- /dev/null +++ b/src/executable/setup/FileRetriever.ts @@ -0,0 +1,22 @@ +import fs from "fs"; +import path from "path"; + +export namespace FileRetriever { + export const directory = + (name: string) => + (dir: string, depth: number = 0): string | null => { + const location: string = path.join(dir, name); + if (fs.existsSync(location)) return dir; + else if (depth > 2) return null; + return directory(name)(path.join(dir, ".."), depth + 1); + }; + + export const file = + (name: string) => + (directory: string, depth: number = 0): string | null => { + const location: string = path.join(directory, name); + if (fs.existsSync(location)) return location; + else if (depth > 2) return null; + return file(name)(path.join(directory, ".."), depth + 1); + }; +} diff --git a/src/executable/setup/PackageManager.ts b/src/executable/setup/PackageManager.ts new file mode 100644 index 0000000000..fd4401fc74 --- /dev/null +++ b/src/executable/setup/PackageManager.ts @@ -0,0 +1,86 @@ +import fs from "fs"; +import path from "path"; + +import { CommandExecutor } from "./CommandExecutor"; +import { FileRetriever } from "./FileRetriever"; + +const managers = ["npm", "pnpm", "yarn", "bun"] as const; +type Manager = (typeof managers)[number]; + +export class PackageManager { + public manager: Manager = "npm"; + public get file(): string { + return path.join(this.directory, "package.json"); + } + + public static async mount(): Promise { + const location: string | null = await FileRetriever.directory( + "package.json", + )(process.cwd()); + if (location === null) + throw new URIError(`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; + modulo: string; + version: string; + }): boolean { + const cmd = installCmdTable[this.manager]; + const option = props.dev ? devOptionTable[this.manager] : ""; + const middle: string = `${cmd} ${option}` as const; + CommandExecutor.run( + `${this.manager} ${middle} ${props.modulo}${ + props.version ? `@${props.version}` : "" + }`, + ); + return true; + } + + 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; + } +} + +const installCmdTable = { + npm: "install", + pnpm: "add", + yarn: "add", + bun: "add", +} as const satisfies Record; +const devOptionTable = { + npm: "--save-dev", + pnpm: "--save-dev", + yarn: "--dev", + bun: "--dev", +} as const satisfies Record; diff --git a/src/executable/setup/PluginConfigurator.ts b/src/executable/setup/PluginConfigurator.ts new file mode 100644 index 0000000000..a1e80af8e1 --- /dev/null +++ b/src/executable/setup/PluginConfigurator.ts @@ -0,0 +1,69 @@ +import comments from "comment-json"; +import fs from "fs"; + +import { TypiaSetupWizard } from "../TypiaSetupWizard"; + +export namespace PluginConfigurator { + export async function configure( + args: TypiaSetupWizard.IArguments, + ): Promise { + // GET COMPILER-OPTIONS + const config: comments.CommentObject = comments.parse( + await fs.promises.readFile(args.project!, "utf8"), + ) as comments.CommentObject; + const compilerOptions = config.compilerOptions as + | comments.CommentObject + | undefined; + if (compilerOptions === undefined) + throw new ReferenceError( + `${args.project} file does not have "compilerOptions" property.`, + ); + + // PREPARE PLUGINS + const plugins: comments.CommentArray = (() => { + const plugins = compilerOptions.plugins as + | comments.CommentArray + | undefined; + if (plugins === undefined) return (compilerOptions.plugins = [] as any); + else if (!Array.isArray(plugins)) + throw new TypeError( + `"plugins" property of ${args.project} must be array type.`, + ); + return plugins; + })(); + + const strict: boolean | undefined = compilerOptions.strict as + | boolean + | undefined; + const strictNullChecks: boolean | undefined = + compilerOptions.strictNullChecks as boolean | undefined; + const oldbie: comments.CommentObject | undefined = plugins.find( + (p) => + typeof p === "object" && + p !== null && + p.transform === "typia/lib/transform", + ); + if ( + strictNullChecks !== false && + (strict === true || strictNullChecks === true) && + oldbie !== undefined + ) + return; + + // DO CONFIGURE + compilerOptions.strictNullChecks = true; + if (strict === undefined && strictNullChecks === undefined) + compilerOptions.strict = true; + if (oldbie === undefined) + plugins.push( + comments.parse(` + { + "transform": "typia/lib/transform" + }`) as comments.CommentObject, + ); + await fs.promises.writeFile( + args.project!, + comments.stringify(config, null, 2), + ); + } +} diff --git a/src/executable/typia.ts b/src/executable/typia.ts new file mode 100644 index 0000000000..bcdde5793a --- /dev/null +++ b/src/executable/typia.ts @@ -0,0 +1,55 @@ +#!/usr/bin/env node +const USAGE = `Wrong command has been detected. Use like below: + + npx typia setup \\ + --manager (npm|pnpm|yarn) \\ + --project {tsconfig.json file path} + + - npx typia setup + - npx typia setup --manager pnpm + - npx typia setup --project tsconfig.test.json + + npx typia generate + --input {directory} \\ + --output {directory} + + --npx typia generate --input src/templates --output src/functinoal +`; + +const halt = (desc: string): never => { + console.error(desc); + process.exit(-1); +}; + +const main = async (): Promise => { + try { + await import("comment-json"); + await import("inquirer"); + await import("commander"); + } catch { + halt(`typia has not been installed. Run "npm i typia" before.`); + } + + const type: string | undefined = process.argv[2]; + if (type === "setup") { + const { TypiaSetupWizard } = await import("./TypiaSetupWizard"); + await TypiaSetupWizard.setup(); + } else if (type === "patch") { + const { TypiaPatchWizard } = await import("./TypiaPatchWizard"); + await TypiaPatchWizard.main(); + } else if (type === "generate") { + try { + await import("typescript"); + } catch { + halt( + `typescript has not been installed. Run "npm i -D typescript" before.`, + ); + } + const { TypiaGenerateWizard } = await import("./TypiaGenerateWizard"); + await TypiaGenerateWizard.generate(); + } else halt(USAGE); +}; +main().catch((exp) => { + console.error(exp); + process.exit(-1); +}); diff --git a/test-esm/package.json b/test-esm/package.json index 418a3f92df..e688ccb6f9 100644 --- a/test-esm/package.json +++ b/test-esm/package.json @@ -36,6 +36,6 @@ "typescript": "^5.4.5" }, "dependencies": { - "typia": "../typia-6.10.0-dev.20240910.tgz" + "typia": "../typia-6.10.0-dev.20240910-2.tgz" } } \ No newline at end of file diff --git a/test/package.json b/test/package.json index 6a16627ff0..b9a7515113 100644 --- a/test/package.json +++ b/test/package.json @@ -52,6 +52,6 @@ "suppress-warnings": "^1.0.2", "tstl": "^3.0.0", "uuid": "^9.0.1", - "typia": "../typia-6.10.0-dev.20240910.tgz" + "typia": "../typia-6.10.0-dev.20240910-2.tgz" } } \ No newline at end of file