Skip to content

Commit 714233e

Browse files
committed
Develop #509 - npx typia generate
1 parent 4e0db58 commit 714233e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+9301
-435
lines changed

.prettierignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
lib
2-
node_modules
2+
node_modules
3+
4+
test/features/generate/input/generate_assert_clone.ts

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export function assertStringify<T>(input: T): string; // safe and faster
2525

2626
// MISC
2727
export function random<T>(): Primitive<T>; // generate random data
28-
export function clone<T>(input: T): Primitive<T>; // hard copy
28+
export function clone<T>(input: T): Primitive<T>; // deep clone
2929
export function prune<T extends object>(input: T): void; // erase extra props
3030
// +) isClone, assertClone, validateClone
3131
// +) isPrune, assertPrune, validatePrune
@@ -233,6 +233,8 @@ export function createAssertStringify<T>(): (input: T) => string;
233233
export function random<T>(): Primitive<T>; // random data generator
234234
export function clone<T>(input: T): Primitive<T>; // deep copy
235235
export function prune<T>(input: T): void; // remove superfluous properties
236+
// +) isClone, assertClone, validateClone
237+
// +) isPrune, assertPrune, validatePrune
236238
```
237239

238240
When you need test data, just generate it through `typia.random<T>()`.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "typia",
3-
"version": "3.5.3",
3+
"version": "3.6.0-dev.20230219",
44
"description": "Superfast runtime validators with only one line",
55
"main": "lib/index.js",
66
"typings": "lib/index.d.ts",

src/executable/TypiaGenerateWizard.ts

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import fs from "fs";
2+
3+
import { TypiaGenerator } from "../generate/TypiaGenerator";
4+
import { ArgumentParser } from "./setup/ArgumentParser";
5+
import { PackageManager } from "./setup/PackageManager";
6+
7+
export namespace TypiaGenerateWizard {
8+
export async function generate(): Promise<void> {
9+
console.log("----------------------------------------");
10+
console.log(" Typia Generate Wizard");
11+
console.log("----------------------------------------");
12+
13+
// LOAD PACKAGE.JSON INFO
14+
const pack: PackageManager = await PackageManager.mount();
15+
const options: IArguments = await ArgumentParser.parse(pack)(false)(
16+
inquiry,
17+
);
18+
await TypiaGenerator.generate(options);
19+
}
20+
21+
const inquiry: ArgumentParser.Inquiry<IArguments> = async (
22+
_pack,
23+
command,
24+
prompt,
25+
action,
26+
) => {
27+
// PREPARE ASSETS
28+
command.option("--input <path>", "input directory");
29+
command.option("--output <directory>", "output directory");
30+
command.option("--project [project]", "tsconfig.json file location");
31+
32+
const questioned = { value: false };
33+
34+
const input = (name: string) => async (message: string) => {
35+
const result = await prompt()({
36+
type: "input",
37+
name,
38+
message,
39+
default: "",
40+
});
41+
return result[name] as string;
42+
};
43+
const select =
44+
(name: string) =>
45+
(message: string) =>
46+
async <Choice extends string>(
47+
choices: Choice[],
48+
): Promise<Choice> => {
49+
questioned.value = true;
50+
return (
51+
await prompt()({
52+
type: "list",
53+
name: name,
54+
message: message,
55+
choices: choices,
56+
})
57+
)[name];
58+
};
59+
const configure = async () => {
60+
const fileList: string[] = await (
61+
await fs.promises.readdir(process.cwd())
62+
).filter(
63+
(str) =>
64+
str.substring(0, 8) === "tsconfig" &&
65+
str.substring(str.length - 5) === ".json",
66+
);
67+
if (fileList.length === 0)
68+
throw new Error(`Unable to find "tsconfig.json" file.`);
69+
else if (fileList.length === 1) return fileList[0];
70+
return select("tsconfig")("TS Config File")(fileList);
71+
};
72+
73+
return action(async (options) => {
74+
options.input ??= await input("input")("input directory");
75+
options.output ??= await input("output")("output directory");
76+
options.project ??= await configure();
77+
return options as IArguments;
78+
});
79+
};
80+
81+
export interface IArguments {
82+
input: string;
83+
output: string;
84+
project: string;
85+
}
86+
}

src/executable/internal/ArgumentParser.ts src/executable/TypiaSetupWizard.ts

+58-60
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,71 @@
1-
import type CommanderModule from "commander";
21
import fs from "fs";
3-
import type * as InquirerModule from "inquirer";
42
import path from "path";
53

6-
import { PackageManager } from "./PackageManager";
4+
import { ArgumentParser } from "./setup/ArgumentParser";
5+
import { CommandExecutor } from "./setup/CommandExecutor";
6+
import { PackageManager } from "./setup/PackageManager";
7+
import { PluginConfigurator } from "./setup/PluginConfigurator";
78

8-
export namespace ArgumentParser {
9+
export namespace TypiaSetupWizard {
910
export interface IArguments {
1011
compiler: "ts-patch" | "ttypescript";
1112
manager: "npm" | "pnpm" | "yarn";
1213
project: string | null;
1314
}
1415

15-
export async function parse(pack: PackageManager): Promise<IArguments> {
16-
// INSTALL TEMPORARY PACKAGES
17-
const newbie = {
18-
commander: pack.install({
19-
dev: true,
20-
modulo: "commander",
21-
version: "10.0.0",
22-
silent: true,
23-
}),
24-
inquirer: pack.install({
25-
dev: true,
26-
modulo: "inquirer",
27-
version: "8.2.5",
28-
silent: true,
29-
}),
30-
};
16+
export async function setup(): Promise<void> {
17+
console.log("----------------------------------------");
18+
console.log(" Typia Setup Wizard");
19+
console.log("----------------------------------------");
3120

32-
// TAKE OPTIONS
33-
const output: IArguments | Error = await (async () => {
34-
try {
35-
return await _Parse(pack);
36-
} catch (error) {
37-
return error as Error;
38-
}
21+
// PREPARE ASSETS
22+
const pack: PackageManager = await PackageManager.mount();
23+
const args: IArguments = await ArgumentParser.parse(pack)(true)(
24+
inquiry,
25+
);
26+
27+
// INSTALL TYPESCRIPT
28+
pack.install({ dev: true, modulo: "typescript" });
29+
args.project ??= (() => {
30+
CommandExecutor.run("npx tsc --init", false);
31+
return (args.project = "tsconfig.json");
3932
})();
33+
pack.install({ dev: true, modulo: "ts-node" });
4034

41-
// REMOVE TEMPORARY PACKAGES
42-
if (newbie.commander) pack.erase({ modulo: "commander", silent: true });
43-
if (newbie.inquirer) pack.erase({ modulo: "inquirer", silent: true });
35+
// INSTALL COMPILER
36+
pack.install({ dev: true, modulo: args.compiler });
37+
if (args.compiler === "ts-patch") {
38+
await pack.save((data) => {
39+
data.scripts ??= {};
40+
if (
41+
typeof data.scripts.prepare === "string" &&
42+
data.scripts.prepare.indexOf("ts-patch install") === -1
43+
)
44+
data.scripts.prepare =
45+
"ts-patch install && " + data.scripts.prepare;
46+
else data.scripts.prepare = "ts-patch install";
47+
});
48+
CommandExecutor.run("npm run prepare", false);
49+
}
4450

45-
// RETURNS
46-
if (output instanceof Error) throw output;
47-
return output;
51+
// INSTALL AND CONFIGURE TYPIA
52+
pack.install({ dev: false, modulo: "typia" });
53+
await PluginConfigurator.configure(pack, args);
4854
}
4955

50-
async function _Parse(pack: PackageManager): Promise<IArguments> {
56+
const inquiry: ArgumentParser.Inquiry<IArguments> = async (
57+
pack,
58+
command,
59+
prompt,
60+
action,
61+
) => {
5162
// PREPARE ASSETS
52-
const { createPromptModule }: typeof InquirerModule = await import(
53-
path.join(pack.directory, "node_modules", "inquirer")
54-
);
55-
const { program }: typeof CommanderModule = await import(
56-
path.join(pack.directory, "node_modules", "commander")
57-
);
58-
59-
program.option("--compiler [compiler]", "compiler type");
60-
program.option("--manager [manager", "package manager");
61-
program.option("--project [project]", "tsconfig.json file location");
63+
command.option("--compiler [compiler]", "compiler type");
64+
command.option("--manager [manager", "package manager");
65+
command.option("--project [project]", "tsconfig.json file location");
6266

6367
// INTERNAL PROCEDURES
6468
const questioned = { value: false };
65-
const action = (
66-
closure: (options: Partial<IArguments>) => Promise<IArguments>,
67-
) => {
68-
return new Promise<IArguments>((resolve, reject) => {
69-
program.action(async (options) => {
70-
try {
71-
resolve(await closure(options));
72-
} catch (exp) {
73-
reject(exp);
74-
}
75-
});
76-
program.parseAsync().catch(reject);
77-
});
78-
};
7969
const select =
8070
(name: string) =>
8171
(message: string) =>
@@ -84,7 +74,7 @@ export namespace ArgumentParser {
8474
): Promise<Choice> => {
8575
questioned.value = true;
8676
return (
87-
await createPromptModule()({
77+
await prompt()({
8878
type: "list",
8979
name: name,
9080
message: message,
@@ -113,7 +103,7 @@ export namespace ArgumentParser {
113103
if (options.compiler === undefined) {
114104
console.log(COMPILER_DESCRIPTION);
115105
options.compiler = await select("compiler")(`Compiler`)(
116-
pack.data.scripts?.build === "nest build"
106+
is_nest_cli(pack)
117107
? ["ts-patch" as const, "ttypescript" as const]
118108
: ["ttypescript" as const, "ts-patch" as const],
119109
);
@@ -129,6 +119,14 @@ export namespace ArgumentParser {
129119
if (questioned.value) console.log("");
130120
return options as IArguments;
131121
});
122+
};
123+
124+
function is_nest_cli(pack: PackageManager): boolean {
125+
return (
126+
(typeof pack.data.scripts?.build === "string" &&
127+
pack.data.scripts.build.indexOf("nest build") !== -1) ||
128+
fs.existsSync(path.join(pack.directory, "nest-cli.json"))
129+
);
132130
}
133131
}
134132

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import type CommanderModule from "commander";
2+
import type * as InquirerModule from "inquirer";
3+
import path from "path";
4+
5+
import { PackageManager } from "./PackageManager";
6+
7+
export namespace ArgumentParser {
8+
export type Inquiry<T> = (
9+
pack: PackageManager,
10+
command: CommanderModule.Command,
11+
prompt: (
12+
opt?: InquirerModule.StreamOptions,
13+
) => InquirerModule.PromptModule,
14+
action: (closure: (options: Partial<T>) => Promise<T>) => Promise<T>,
15+
) => Promise<T>;
16+
17+
export const parse =
18+
(pack: PackageManager) =>
19+
(erase: boolean) =>
20+
async <T>(
21+
inquiry: (
22+
pack: PackageManager,
23+
command: CommanderModule.Command,
24+
prompt: (
25+
opt?: InquirerModule.StreamOptions,
26+
) => InquirerModule.PromptModule,
27+
action: (
28+
closure: (options: Partial<T>) => Promise<T>,
29+
) => Promise<T>,
30+
) => Promise<T>,
31+
): Promise<T> => {
32+
// INSTALL TEMPORARY PACKAGES
33+
const newbie = {
34+
commander: pack.install({
35+
dev: true,
36+
modulo: "commander",
37+
version: "10.0.0",
38+
silent: true,
39+
}),
40+
inquirer: pack.install({
41+
dev: true,
42+
modulo: "inquirer",
43+
version: "8.2.5",
44+
silent: true,
45+
}),
46+
};
47+
48+
// LOAD INSTALLED MODULES
49+
const { program: command }: typeof CommanderModule = await import(
50+
path.join(pack.directory, "node_modules", "commander")
51+
);
52+
const { createPromptModule: prompt }: typeof InquirerModule =
53+
await import(
54+
path.join(pack.directory, "node_modules", "inquirer")
55+
);
56+
57+
// TAKE OPTIONS
58+
const action = (closure: (options: Partial<T>) => Promise<T>) =>
59+
new Promise<T>((resolve, reject) => {
60+
command.action(async (options) => {
61+
try {
62+
resolve(await closure(options));
63+
} catch (exp) {
64+
reject(exp);
65+
}
66+
});
67+
command.parseAsync().catch(reject);
68+
});
69+
const output: T | Error = await (async () => {
70+
try {
71+
return await inquiry(pack, command, prompt, action);
72+
} catch (error) {
73+
return error as Error;
74+
}
75+
})();
76+
77+
// REMOVE TEMPORARY PACKAGES
78+
if (erase === true) {
79+
if (newbie.commander)
80+
pack.erase({ modulo: "commander", silent: true });
81+
if (newbie.inquirer)
82+
pack.erase({ modulo: "inquirer", silent: true });
83+
}
84+
85+
// RETURNS
86+
if (output instanceof Error) throw output;
87+
return output;
88+
};
89+
}

src/executable/internal/PluginConfigurator.ts src/executable/setup/PluginConfigurator.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import type Comment from "comment-json";
22
import fs from "fs";
33
import path from "path";
44

5-
import { ArgumentParser } from "./ArgumentParser";
5+
import { TypiaSetupWizard } from "../TypiaSetupWizard";
66
import { PackageManager } from "./PackageManager";
77

88
export namespace PluginConfigurator {
99
export async function configure(
1010
pack: PackageManager,
11-
args: ArgumentParser.IArguments,
11+
args: TypiaSetupWizard.IArguments,
1212
): Promise<void> {
1313
// INSTALL COMMENT-JSON
1414
const installed: boolean = pack.install({
@@ -39,7 +39,7 @@ export namespace PluginConfigurator {
3939

4040
async function _Configure(
4141
pack: PackageManager,
42-
args: ArgumentParser.IArguments,
42+
args: TypiaSetupWizard.IArguments,
4343
): Promise<void> {
4444
// GET COMPILER-OPTIONS
4545
const Comment: typeof import("comment-json") = await import(

0 commit comments

Comments
 (0)