Skip to content

Commit

Permalink
Develop #509 - npx typia generate
Browse files Browse the repository at this point in the history
  • Loading branch information
samchon committed Feb 19, 2023
1 parent 4e0db58 commit 714233e
Show file tree
Hide file tree
Showing 48 changed files with 9,301 additions and 435 deletions.
4 changes: 3 additions & 1 deletion .prettierignore
@@ -1,2 +1,4 @@
lib
node_modules
node_modules

test/features/generate/input/generate_assert_clone.ts
4 changes: 3 additions & 1 deletion README.md
Expand Up @@ -25,7 +25,7 @@ export function assertStringify<T>(input: T): string; // safe and faster

// MISC
export function random<T>(): Primitive<T>; // generate random data
export function clone<T>(input: T): Primitive<T>; // hard copy
export function clone<T>(input: T): Primitive<T>; // deep clone
export function prune<T extends object>(input: T): void; // erase extra props
// +) isClone, assertClone, validateClone
// +) isPrune, assertPrune, validatePrune
Expand Down Expand Up @@ -233,6 +233,8 @@ export function createAssertStringify<T>(): (input: T) => string;
export function random<T>(): Primitive<T>; // random data generator
export function clone<T>(input: T): Primitive<T>; // deep copy
export function prune<T>(input: T): void; // remove superfluous properties
// +) isClone, assertClone, validateClone
// +) isPrune, assertPrune, validatePrune
```

When you need test data, just generate it through `typia.random<T>()`.
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "typia",
"version": "3.5.3",
"version": "3.6.0-dev.20230219",
"description": "Superfast runtime validators with only one line",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
Expand Down
86 changes: 86 additions & 0 deletions src/executable/TypiaGenerateWizard.ts
@@ -0,0 +1,86 @@
import fs from "fs";

import { TypiaGenerator } from "../generate/TypiaGenerator";
import { ArgumentParser } from "./setup/ArgumentParser";
import { PackageManager } from "./setup/PackageManager";

export namespace TypiaGenerateWizard {
export async function generate(): Promise<void> {
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)(false)(
inquiry,
);
await TypiaGenerator.generate(options);
}

const inquiry: ArgumentParser.Inquiry<IArguments> = 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 <Choice extends string>(
choices: Choice[],
): Promise<Choice> => {
questioned.value = true;
return (
await prompt()({
type: "list",
name: name,
message: message,
choices: choices,
})
)[name];
};
const configure = async () => {
const fileList: string[] = await (
await fs.promises.readdir(process.cwd())
).filter(
(str) =>
str.substring(0, 8) === "tsconfig" &&
str.substring(str.length - 5) === ".json",
);
if (fileList.length === 0)
throw new Error(`Unable to find "tsconfig.json" file.`);
else if (fileList.length === 1) return fileList[0];
return select("tsconfig")("TS Config File")(fileList);
};

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;
}
}
@@ -1,81 +1,71 @@
import type CommanderModule from "commander";
import fs from "fs";
import type * as InquirerModule from "inquirer";
import path from "path";

import { PackageManager } from "./PackageManager";
import { ArgumentParser } from "./setup/ArgumentParser";
import { CommandExecutor } from "./setup/CommandExecutor";
import { PackageManager } from "./setup/PackageManager";
import { PluginConfigurator } from "./setup/PluginConfigurator";

export namespace ArgumentParser {
export namespace TypiaSetupWizard {
export interface IArguments {
compiler: "ts-patch" | "ttypescript";
manager: "npm" | "pnpm" | "yarn";
project: string | null;
}

export async function parse(pack: PackageManager): Promise<IArguments> {
// INSTALL TEMPORARY PACKAGES
const newbie = {
commander: pack.install({
dev: true,
modulo: "commander",
version: "10.0.0",
silent: true,
}),
inquirer: pack.install({
dev: true,
modulo: "inquirer",
version: "8.2.5",
silent: true,
}),
};
export async function setup(): Promise<void> {
console.log("----------------------------------------");
console.log(" Typia Setup Wizard");
console.log("----------------------------------------");

// TAKE OPTIONS
const output: IArguments | Error = await (async () => {
try {
return await _Parse(pack);
} catch (error) {
return error as Error;
}
// PREPARE ASSETS
const pack: PackageManager = await PackageManager.mount();
const args: IArguments = await ArgumentParser.parse(pack)(true)(
inquiry,
);

// INSTALL TYPESCRIPT
pack.install({ dev: true, modulo: "typescript" });
args.project ??= (() => {
CommandExecutor.run("npx tsc --init", false);
return (args.project = "tsconfig.json");
})();
pack.install({ dev: true, modulo: "ts-node" });

// REMOVE TEMPORARY PACKAGES
if (newbie.commander) pack.erase({ modulo: "commander", silent: true });
if (newbie.inquirer) pack.erase({ modulo: "inquirer", silent: true });
// INSTALL COMPILER
pack.install({ dev: true, modulo: args.compiler });
if (args.compiler === "ts-patch") {
await pack.save((data) => {
data.scripts ??= {};
if (
typeof data.scripts.prepare === "string" &&
data.scripts.prepare.indexOf("ts-patch install") === -1
)
data.scripts.prepare =
"ts-patch install && " + data.scripts.prepare;
else data.scripts.prepare = "ts-patch install";
});
CommandExecutor.run("npm run prepare", false);
}

// RETURNS
if (output instanceof Error) throw output;
return output;
// INSTALL AND CONFIGURE TYPIA
pack.install({ dev: false, modulo: "typia" });
await PluginConfigurator.configure(pack, args);
}

async function _Parse(pack: PackageManager): Promise<IArguments> {
const inquiry: ArgumentParser.Inquiry<IArguments> = async (
pack,
command,
prompt,
action,
) => {
// PREPARE ASSETS
const { createPromptModule }: typeof InquirerModule = await import(
path.join(pack.directory, "node_modules", "inquirer")
);
const { program }: typeof CommanderModule = await import(
path.join(pack.directory, "node_modules", "commander")
);

program.option("--compiler [compiler]", "compiler type");
program.option("--manager [manager", "package manager");
program.option("--project [project]", "tsconfig.json file location");
command.option("--compiler [compiler]", "compiler type");
command.option("--manager [manager", "package manager");
command.option("--project [project]", "tsconfig.json file location");

// INTERNAL PROCEDURES
const questioned = { value: false };
const action = (
closure: (options: Partial<IArguments>) => Promise<IArguments>,
) => {
return new Promise<IArguments>((resolve, reject) => {
program.action(async (options) => {
try {
resolve(await closure(options));
} catch (exp) {
reject(exp);
}
});
program.parseAsync().catch(reject);
});
};
const select =
(name: string) =>
(message: string) =>
Expand All @@ -84,7 +74,7 @@ export namespace ArgumentParser {
): Promise<Choice> => {
questioned.value = true;
return (
await createPromptModule()({
await prompt()({
type: "list",
name: name,
message: message,
Expand Down Expand Up @@ -113,7 +103,7 @@ export namespace ArgumentParser {
if (options.compiler === undefined) {
console.log(COMPILER_DESCRIPTION);
options.compiler = await select("compiler")(`Compiler`)(
pack.data.scripts?.build === "nest build"
is_nest_cli(pack)
? ["ts-patch" as const, "ttypescript" as const]
: ["ttypescript" as const, "ts-patch" as const],
);
Expand All @@ -129,6 +119,14 @@ export namespace ArgumentParser {
if (questioned.value) console.log("");
return options as IArguments;
});
};

function is_nest_cli(pack: PackageManager): boolean {
return (
(typeof pack.data.scripts?.build === "string" &&
pack.data.scripts.build.indexOf("nest build") !== -1) ||
fs.existsSync(path.join(pack.directory, "nest-cli.json"))
);
}
}

Expand Down
89 changes: 89 additions & 0 deletions src/executable/setup/ArgumentParser.ts
@@ -0,0 +1,89 @@
import type CommanderModule from "commander";
import type * as InquirerModule from "inquirer";
import path from "path";

import { PackageManager } from "./PackageManager";

export namespace ArgumentParser {
export type Inquiry<T> = (
pack: PackageManager,
command: CommanderModule.Command,
prompt: (
opt?: InquirerModule.StreamOptions,
) => InquirerModule.PromptModule,
action: (closure: (options: Partial<T>) => Promise<T>) => Promise<T>,
) => Promise<T>;

export const parse =
(pack: PackageManager) =>
(erase: boolean) =>
async <T>(
inquiry: (
pack: PackageManager,
command: CommanderModule.Command,
prompt: (
opt?: InquirerModule.StreamOptions,
) => InquirerModule.PromptModule,
action: (
closure: (options: Partial<T>) => Promise<T>,
) => Promise<T>,
) => Promise<T>,
): Promise<T> => {
// INSTALL TEMPORARY PACKAGES
const newbie = {
commander: pack.install({
dev: true,
modulo: "commander",
version: "10.0.0",
silent: true,
}),
inquirer: pack.install({
dev: true,
modulo: "inquirer",
version: "8.2.5",
silent: true,
}),
};

// LOAD INSTALLED MODULES
const { program: command }: typeof CommanderModule = await import(
path.join(pack.directory, "node_modules", "commander")
);
const { createPromptModule: prompt }: typeof InquirerModule =
await import(
path.join(pack.directory, "node_modules", "inquirer")
);

// TAKE OPTIONS
const action = (closure: (options: Partial<T>) => Promise<T>) =>
new Promise<T>((resolve, reject) => {
command.action(async (options) => {
try {
resolve(await closure(options));
} catch (exp) {
reject(exp);
}
});
command.parseAsync().catch(reject);
});
const output: T | Error = await (async () => {
try {
return await inquiry(pack, command, prompt, action);
} catch (error) {
return error as Error;
}
})();

// REMOVE TEMPORARY PACKAGES
if (erase === true) {
if (newbie.commander)
pack.erase({ modulo: "commander", silent: true });
if (newbie.inquirer)
pack.erase({ modulo: "inquirer", silent: true });
}

// RETURNS
if (output instanceof Error) throw output;
return output;
};
}
File renamed without changes.
File renamed without changes.
Expand Up @@ -2,13 +2,13 @@ import type Comment from "comment-json";
import fs from "fs";
import path from "path";

import { ArgumentParser } from "./ArgumentParser";
import { TypiaSetupWizard } from "../TypiaSetupWizard";
import { PackageManager } from "./PackageManager";

export namespace PluginConfigurator {
export async function configure(
pack: PackageManager,
args: ArgumentParser.IArguments,
args: TypiaSetupWizard.IArguments,
): Promise<void> {
// INSTALL COMMENT-JSON
const installed: boolean = pack.install({
Expand Down Expand Up @@ -39,7 +39,7 @@ export namespace PluginConfigurator {

async function _Configure(
pack: PackageManager,
args: ArgumentParser.IArguments,
args: TypiaSetupWizard.IArguments,
): Promise<void> {
// GET COMPILER-OPTIONS
const Comment: typeof import("comment-json") = await import(
Expand Down

0 comments on commit 714233e

Please sign in to comment.