nestia 1.0.0 → 2.0.0-dev.20220413-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ # These are supported funding model platforms
2
+
3
+ open_collective: nestia
package/README.md CHANGED
@@ -7,8 +7,14 @@ Automatic SDK generator for the NestJS.
7
7
  [![Build Status](https://github.com/samchon/nestia/workflows/build/badge.svg)](https://github.com/samchon/nestia/actions?query=workflow%3Abuild)
8
8
 
9
9
  ```bash
10
+ # INSTALL NESTIA
10
11
  npm install --save-dev nestia
12
+
13
+ # WHEN ALL OF THE CONTROLLRES ARE GATHERED INTO A DIRECTORY
11
14
  npx nestia sdk "src/controller" --out "src/api"
15
+
16
+ # REGULAR NESTJS PATTERN
17
+ npx nestia sdk "src/**/*.controller.ts" --out "src/api"
12
18
  ```
13
19
 
14
20
  Don't write any `swagger` comment. Just deliver the SDK.
@@ -60,13 +66,15 @@ Just type the `npm install --save-dev nestia` command in your NestJS backend pro
60
66
  ```bash
61
67
  npx nestia sdk <source_controller_directory> --out <output_sdk_directory>
62
68
 
69
+ npx nestia sdk "src/**/*.controller.ts" --out "src/api"
63
70
  npx nestia sdk "src/controllers" --out "src/api"
64
- npx nestia sdk "src/controllers/consumers" "src/controllers/sellers" --out "src/api
71
+ npx nestia sdk "src/controllers/consumers" "src/controllers/sellers" --out "src/api"
72
+ npx nestia sdk "src/controllers" --exclude "src/**/Fake*.ts" --out "src/api"
65
73
  ```
66
74
 
67
75
  To generate a SDK library through the **Nestia** is very easy.
68
76
 
69
- Just type the `nestia sdk <input> --out <output>` command in the console. If there're multiple source directories containing the NestJS controller classes, type all of them separating by a `space` word.
77
+ Just type the `nestia sdk <input> --out <output>` command in the console. When there're multiple source directories containing the NestJS controller classes, type all of them separating by a `space` word. If you want to exclude some directories or files from the SDK generation, the `--exclude` option would be useful.
70
78
 
71
79
  Also, when generating a SDK using the cli options, `compilerOptions` would follow the `tsconfig.json`, that is configured for the backend server. If no `tsconfig.json` file exists in your project, the configuration would be default option (`ES5` with `strict` mode). If you want to use different `compilerOptions` with the `tsconfig.json`, you should configure the [nestia.config.ts](#nestiaconfigts).
72
80
 
@@ -89,26 +97,38 @@ Therefore, when you publish an SDK library generated by this **Nestia**, you hav
89
97
  ## Advanced
90
98
  ### `nestia.config.ts`
91
99
  ```typescript
92
- export namespace NestiaApplication
100
+ export interface IConfiguration
93
101
  {
94
- export interface IConfiguration
95
- {
96
- /**
97
- * List of directories containing the NestJS controller classes.
98
- */
99
- input: string | string[];
102
+ /**
103
+ * List of files or directories containing the NestJS controller classes.
104
+ */
105
+ input: string | string[] | IConfiguration.IInput;
100
106
 
107
+ /**
108
+ * Output directory that SDK would be placed in.
109
+ */
110
+ output: string;
111
+
112
+ /**
113
+ * Compiler options for the TypeScript.
114
+ *
115
+ * If omitted, the configuration would follow the `tsconfig.json`.
116
+ */
117
+ compilerOptions?: tsc.CompilerOptions
118
+ }
119
+ export namespace IConfiguration
120
+ {
121
+ export interface IInput
122
+ {
101
123
  /**
102
- * Output directory that SDK would be placed in.
124
+ * List of files or directories containing the NestJS controller classes.
103
125
  */
104
- output: string;
126
+ include: string[];
105
127
 
106
128
  /**
107
- * Compiler options for the TypeScript.
108
- *
109
- * If omitted, the configuration would follow the `tsconfig.json`.
129
+ * List of files or directories to be excluded.
110
130
  */
111
- compilerOptions?: tsc.CompilerOptions
131
+ exclude: string[];
112
132
  }
113
133
  }
114
134
  ```
@@ -119,11 +139,24 @@ Write below content as the `nestia.config.ts` file and place it onto the root di
119
139
 
120
140
  ```typescript
121
141
  export = {
122
- input: "src/controllers`",
142
+ input: "src/controllers",
123
143
  output: "src/api"
124
144
  };
125
145
  ```
126
146
 
147
+ > Alternative options for the regular NestJS project:
148
+ >
149
+ > ```typescript
150
+ > export = {
151
+ > input: "src/**/*.controller.ts",
152
+ > /* input: {
153
+ > include: ["src/controllers/*.controller.ts"],
154
+ > exclude: ["src/controllers/fake_*.controller.ts"]
155
+ > },*/
156
+ > output: "src/api"
157
+ > }
158
+ > ```
159
+
127
160
 
128
161
 
129
162
  ### Recommended Structures
@@ -214,18 +247,15 @@ export function store
214
247
  connection: IConnection,
215
248
  section: string,
216
249
  saleId: number,
217
- input: store.Input
250
+ input: Primitive<store.Input>
218
251
  ): Promise<store.Output>
219
252
  {
220
253
  return Fetcher.fetch
221
254
  (
222
255
  connection,
223
- {
224
- input_encrypted: false,
225
- output_encrypted: false
226
- },
227
- "POST",
228
- `/consumers/${section}/sales/${saleId}/questions/`,
256
+ store.ENCRYPTED,
257
+ store.METHOD,
258
+ store.path(section, saleId),
229
259
  input
230
260
  );
231
261
  }
@@ -233,6 +263,18 @@ export namespace store
233
263
  {
234
264
  export type Input = Primitive<ISaleInquiry.IStore>;
235
265
  export type Output = Primitive<ISaleInquiry<ISaleArticle.IContent>>;
266
+
267
+ export const METHOD = "POST" as const;
268
+ export const PATH: string = "/consumers/:section/sales/:saleId/questions";
269
+ export const ENCRYPTED: Fetcher.IEncrypted = {
270
+ request: true,
271
+ response: true,
272
+ };
273
+
274
+ export function path(section: string, saleId: number): string
275
+ {
276
+ return `/consumers/${section}/sales/${saleId}/questions`;
277
+ }
236
278
  }
237
279
  ```
238
280
 
@@ -240,26 +282,55 @@ export namespace store
240
282
 
241
283
 
242
284
  ## Appendix
243
- ### Safe-TypeORM
244
- https://github.com/samchon/safe-typeorm
285
+ ### Template Project
286
+ https://github.com/samchon/backend
245
287
 
246
- [safe-typeorm](https://github.com/samchon/safe-typeorm) is another library that what I've developed, helping typeorm in the compilation level and optimizes DB performance automatically without any extra dedication.
288
+ I support template backend project using this **Nestia*** library, [backend](https://github.com/samchon/backend).
247
289
 
248
- Therefore, this **Nestia** makes you to be much convenient in the API interaction level and safe-typeorm helps you to be much convenient in the DB interaction level. With those **Nestia** and [safe-typeorm](https://github.com/samchon/safe-typeorm), let's implement the backend server much easily and conveniently.
290
+ Also, reading the README content of the [backend](https://github.com/samchon/backend) template repository, you can find lots of example backend projects who've been generated from the [backend](https://github.com/samchon/backend). Furthermore, the example projects guide how to generate SDK library from the **Nestia** and how to distribute the SDK library thorugh the NPM module.
291
+
292
+ Therefore, if you're planning to compose your own backend project using this **Nestia**, I recommend you to create the repository and learn from the [backend](https://github.com/samchon/backend) template project.
293
+
294
+ ### Nestia-Helper
295
+ https://github.com/samchon/nestia-helper
249
296
 
250
- ### Technial Support
251
- samchon.github@gmail.com
297
+ Helper library of the `NestJS` with **Nestia**.
252
298
 
253
- I can provide technical support about those **Nestia** and [safe-typeorm](https://github.com/samchon/safe-typeorm).
299
+ [nestia-helper](https://github.com/samchon/nestia-helper) is a type of helper library for `Nestia` by enhancing decorator functions. Also, all of the decorator functions provided by this [nestia-helper](https://github.com/samchon/nestia-helper) are all fully compatible with the **Nestia**, who can generate SDK library by analyzing NestJS controller classes in the compilation level.
254
300
 
255
- Therefore, if you have any question or need help, feel free to contact me. If you want to adapt those **Nestia** and [safe-typeorm](https://github.com/samchon/safe-typeorm) in your commercial project, I can provide you the best guidance.
301
+ Of course, this [nestia-helper](https://github.com/samchon/nestia-helper) is not essential for utilizing the `NestJS` and **Nestia**. You can generate SDK library of your NestJS developed backend server without this [nestia-helper](https://github.com/samchon/nestia-helper). However, as decorator functions of this [nestia-helper](https://github.com/samchon/nestia-helper) is enough strong, I recommend you to adapt this [nestia-helper](https://github.com/samchon/nestia-helper) when using `NestJS` and **Nestia**.
256
302
 
257
- I also can help your backend project in the entire development level. If you're suffering by DB architecture design or API structure design, just contact me and get help. I'll help you with my best effort.
303
+ - Supported decorator functions
304
+ - [EncryptedController](https://github.com/samchon/nestia-helper#encryptedcontroller), [EncryptedModule](https://github.com/samchon/nestia-helper#encryptedmodule)
305
+ - [TypedRoute](https://github.com/samchon/nestia-helper#typedroute), [EncryptedRoute](https://github.com/samchon/nestia-helper#encryptedroute)
306
+ - [TypedParam](https://github.com/samchon/nestia-helper#typedparam), [EncryptedBody](https://github.com/samchon/nestia-helper#encryptedbody), [PlainBody](https://github.com/samchon/nestia-helper#plainbody)
307
+ - [ExceptionManager](https://github.com/samchon/nestia-helper#exceptionmanager)
308
+
309
+ ### Safe-TypeORM
310
+ https://github.com/samchon/safe-typeorm
311
+
312
+ [safe-typeorm](https://github.com/samchon/safe-typeorm) is another library that what I've developed, helping `TypeORM` in the compilation level and optimizes DB performance automatically without any extra dedication.
313
+
314
+ Therefore, this **Nestia** makes you to be much convenient in the API interaction level and safe-typeorm helps you to be much convenient in the DB interaction level. With those **Nestia** and [safe-typeorm](https://github.com/samchon/safe-typeorm), let's implement the backend server much easily and conveniently.
315
+
316
+ - When writing [**SQL query**](https://github.com/samchon/safe-typeorm#safe-query-builder),
317
+ - Errors would be detected in the **compilation** level
318
+ - **Auto Completion** would be provided
319
+ - **Type Hint** would be supported
320
+ - You can implement [**App-join**](https://github.com/samchon/safe-typeorm#app-join-builder) very conveniently
321
+ - When [**SELECT**ing for **JSON** conversion](https://github.com/samchon/safe-typeorm#json-select-builder)
322
+ - [**App-Join**](https://github.com/samchon/safe-typeorm#app-join-builder) with the related entities would be automatically done
323
+ - Exact JSON **type** would be automatically **deduced**
324
+ - The **performance** would be **automatically tuned**
325
+ - When [**INSERT**](https://github.com/samchon/safe-typeorm#insert-collection)ing records
326
+ - Sequence of tables would be automatically sorted by analyzing dependencies
327
+ - The **performance** would be **automatically tuned**
328
+
329
+ ![Safe-TypeORM Demo](https://raw.githubusercontent.com/samchon/safe-typeorm/master/assets/demonstrations/safe-query-builder.gif)
258
330
 
259
331
  ### Archidraw
260
332
  https://www.archisketch.com/
261
333
 
262
334
  I have special thanks to the Archidraw, where I'm working for.
263
335
 
264
- The Archidraw is a great IT company developing 3D interior editor and lots of solutions based on the 3D assets. Also, the Archidraw is the first company who had adopted this **Nestia** on their commercial backend project, even this **Nestia** was in the alpha level.
265
-
336
+ The Archidraw is a great IT company developing 3D interior editor and lots of solutions based on the 3D assets. Also, the Archidraw is the first company who had adopted this **Nestia** on their commercial backend project, even this **Nestia** was in the alpha level.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nestia",
3
- "version": "1.0.0",
3
+ "version": "2.0.0-dev.20220413-2",
4
4
  "description": "Automatic SDK and Document generator for the NestJS",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -30,16 +30,21 @@
30
30
  "homepage": "https://github.com/samchon/nestia#readme",
31
31
  "dependencies": {
32
32
  "@types/cli": "^0.11.19",
33
- "@types/node": "^14.14.22",
33
+ "@types/glob": "^7.2.0",
34
+ "@types/node": "^17.0.23",
34
35
  "@types/reflect-metadata": "^0.1.0",
35
36
  "cli": "^1.0.1",
36
37
  "del": "^6.0.0",
38
+ "glob": "^7.2.0",
37
39
  "ts-node": "^9.1.1",
38
- "tstl": "^2.5.0",
39
- "typescript": "^4.3.2"
40
+ "tstl": "^2.5.3",
41
+ "ttypescript": "^1.5.13",
42
+ "typescript": "^4.6.3",
43
+ "typescript-is": "^0.19.0",
44
+ "typescript-transform-paths": "^3.3.1"
40
45
  },
41
46
  "devDependencies": {
42
- "nestia-helper": "^1.0.0",
47
+ "nestia-helper": "^2.0.0",
43
48
  "rimraf": "^3.0.2"
44
49
  }
45
50
  }
@@ -0,0 +1,17 @@
1
+ import type tsc from "typescript";
2
+
3
+ export interface IConfiguration
4
+ {
5
+ input: string | string[] | IConfiguration.IInput;
6
+ output: string;
7
+ compilerOptions?: tsc.CompilerOptions;
8
+ assert?: boolean;
9
+ }
10
+ export namespace IConfiguration
11
+ {
12
+ export interface IInput
13
+ {
14
+ include: string[];
15
+ exclude?: string[];
16
+ }
17
+ }
@@ -1,6 +1,6 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import * as tsc from "typescript";
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import tsc from "typescript";
4
4
  import { Pair } from "tstl/utility/Pair";
5
5
  import { Singleton } from "tstl/thread/Singleton";
6
6
 
@@ -9,16 +9,17 @@ import { ReflectAnalyzer } from "./analyses/ReflectAnalyzer";
9
9
  import { SourceFinder } from "./analyses/SourceFinder";
10
10
  import { SdkGenerator } from "./generates/SdkGenerator";
11
11
 
12
+ import { IConfiguration } from "./IConfiguration";
12
13
  import { IController } from "./structures/IController";
13
14
  import { IRoute } from "./structures/IRoute";
14
15
  import { ArrayUtil } from "./utils/ArrayUtil";
15
16
 
16
17
  export class NestiaApplication
17
18
  {
18
- private readonly config_: NestiaApplication.IConfiguration;
19
+ private readonly config_: IConfiguration;
19
20
  private readonly bundle_checker_: Singleton<Promise<(str: string) => boolean>>;
20
21
 
21
- public constructor(config: NestiaApplication.IConfiguration)
22
+ public constructor(config: IConfiguration)
22
23
  {
23
24
  this.config_ = config;
24
25
  this.bundle_checker_ = new Singleton(async () =>
@@ -47,18 +48,16 @@ export class NestiaApplication
47
48
  public async generate(): Promise<void>
48
49
  {
49
50
  // LOAD CONTROLLER FILES
50
- const fileList: string[] = [];
51
- const inputList: string[] = this.config_.input instanceof Array
52
- ? this.config_.input
53
- : [this.config_.input];
54
-
55
- for (const file of inputList.map(str => path.resolve(str)))
56
- {
57
- const found: string[] = await SourceFinder.find(file);
58
- const filtered: string[] = await ArrayUtil.asyncFilter(found, file => this.is_not_excluded(file));
59
-
60
- fileList.push(...filtered);
61
- }
51
+ const input: IConfiguration.IInput = this.config_.input instanceof Array
52
+ ? { include: this.config_.input }
53
+ : typeof this.config_.input === "string"
54
+ ? { include: [ this.config_.input ] }
55
+ : this.config_.input;
56
+ const fileList: string[] = await ArrayUtil.asyncFilter
57
+ (
58
+ await SourceFinder.find(input),
59
+ file => this.is_not_excluded(file)
60
+ );
62
61
 
63
62
  // ANALYZE REFLECTS
64
63
  const unique: WeakSet<any> = new WeakSet();
@@ -86,7 +85,7 @@ export class NestiaApplication
86
85
  }
87
86
 
88
87
  // DO GENERATE
89
- await SdkGenerator.generate(this.config_.output, routeList);
88
+ await SdkGenerator.generate(this.config_, routeList);
90
89
  }
91
90
 
92
91
  private async is_not_excluded(file: string): Promise<boolean>
@@ -94,14 +93,4 @@ export class NestiaApplication
94
93
  return file.indexOf(`${this.config_.output}${path.sep}functional`) === -1
95
94
  && (await this.bundle_checker_.get())(file) === false;
96
95
  }
97
- }
98
-
99
- export namespace NestiaApplication
100
- {
101
- export interface IConfiguration
102
- {
103
- input: string | string[];
104
- output: string;
105
- compilerOptions?: tsc.CompilerOptions;
106
- }
107
96
  }
@@ -1,5 +1,5 @@
1
- import * as NodePath from "path";
2
- import * as tsc from "typescript";
1
+ import NodePath from "path";
2
+ import tsc from "typescript";
3
3
  import { HashMap } from "tstl/container/HashMap";
4
4
 
5
5
  import { IController } from "../structures/IController";
@@ -1,4 +1,4 @@
1
- import * as tsc from "typescript";
1
+ import tsc from "typescript";
2
2
 
3
3
  export namespace GenericAnalyzer
4
4
  {
@@ -1,4 +1,4 @@
1
- import * as tsc from "typescript";
1
+ import tsc from "typescript";
2
2
 
3
3
  import { HashMap } from "tstl/container/HashMap";
4
4
  import { HashSet } from "tstl/container/HashSet";
@@ -1,33 +1,73 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
1
+ import fs from "fs";
2
+ import glob from "glob";
3
+ import path from "path";
4
+
5
+ import { IConfiguration } from "../IConfiguration";
3
6
 
4
7
  export namespace SourceFinder
5
8
  {
6
- export async function find(directory: string): Promise<string[]>
9
+ export async function find(input: IConfiguration.IInput): Promise<string[]>
7
10
  {
8
- const output: string[] = [];
9
- await gather(output, directory);
11
+ const dict: Set<string> = new Set();
12
+ await decode(input.include, str => dict.add(str));
13
+ if (input.exclude)
14
+ await decode(input.exclude, str => dict.delete(str));
15
+
16
+ return [...dict];
17
+ }
10
18
 
11
- return output;
19
+ async function decode
20
+ (
21
+ input: string[],
22
+ closure: (location: string) => void,
23
+ ): Promise<void>
24
+ {
25
+ for (const pattern of input)
26
+ for (const location of await _Glob(path.resolve(pattern)))
27
+ {
28
+ const stats: fs.Stats = await fs.promises.stat(location);
29
+ if (stats.isDirectory() === true)
30
+ await iterate(closure, location);
31
+ else if (stats.isFile() && _Is_ts_file(location))
32
+ closure(location);
33
+ }
12
34
  }
13
35
 
14
- async function gather(output: string[], directory: string): Promise<void>
36
+ async function iterate
37
+ (
38
+ closure: (location: string) => void,
39
+ location: string
40
+ ): Promise<void>
15
41
  {
16
- const children: string[] = await fs.promises.readdir(directory);
17
- for (const file of children)
42
+ const directory: string[] = await fs.promises.readdir(location);
43
+ for (const file of directory)
18
44
  {
19
- const current: string = `${directory}${path.sep}${file}`;
20
- const stats: fs.Stats = await fs.promises.lstat(current);
45
+ const next: string = path.resolve(`${location}/${file}`);
46
+ const stats: fs.Stats = await fs.promises.stat(next);
21
47
 
22
48
  if (stats.isDirectory() === true)
23
- {
24
- await gather(output, current);
25
- continue;
26
- }
27
- else if (file.substr(-3) !== ".ts" || file.substr(-5) === ".d.ts")
28
- continue;
29
-
30
- output.push(current);
49
+ await iterate(closure, next);
50
+ else if (stats.isFile() && _Is_ts_file(file))
51
+ closure(next);
31
52
  }
32
53
  }
54
+
55
+ function _Glob(pattern: string): Promise<string[]>
56
+ {
57
+ return new Promise((resolve, reject) =>
58
+ {
59
+ glob(pattern, (err, matches) =>
60
+ {
61
+ if (err)
62
+ reject(err);
63
+ else
64
+ resolve(matches.map(str => path.resolve(str)));
65
+ });
66
+ });
67
+ }
68
+
69
+ function _Is_ts_file(file: string): boolean
70
+ {
71
+ return file.substr(-3) === ".ts" && file.substr(-5) !== ".d.ts";
72
+ }
33
73
  }
package/src/bin/nestia.ts CHANGED
@@ -1,117 +1,138 @@
1
- #!/usr/bin/env ts-node-script
1
+ #!/usr/bin/env ts-node
2
2
 
3
- import cli from "cli";
4
- import fs from "fs";
5
- import path from "path";
6
- import tsc from "typescript";
3
+ import * as cp from "child_process";
4
+ import * as fs from "fs";
7
5
 
8
- import { NestiaApplication } from "../NestiaApplication";
9
-
10
- import { Terminal } from "../utils/Terminal";
6
+ import { CompilerOptions } from "../internal/CompilerOptions";
11
7
  import { stripJsonComments } from "../utils/stripJsonComments";
12
8
 
13
- interface ICommand
9
+ interface IConfig
14
10
  {
15
- out: string | null;
11
+ compilerOptions?: CompilerOptions;
16
12
  }
17
13
 
18
- async function sdk(input: string[], command: ICommand): Promise<void>
14
+ function install(): void
19
15
  {
20
- let compilerOptions: tsc.CompilerOptions | undefined = {};
21
-
22
- //----
23
- // NESTIA.CONFIG.TS
24
- //----
25
- if (fs.existsSync("nestia.config.ts") === true)
16
+ // INSTALL DEPENDENCIES
17
+ for (const lib of ["nestia-fetcher", "typescript-is"])
26
18
  {
27
- const config: NestiaApplication.IConfiguration = await import(path.resolve("nestia.config.ts"));
28
- compilerOptions = config.compilerOptions;
29
- input = config.input instanceof Array ? config.input : [config.input];
30
- command.out = config.output;
19
+ const command: string = `npm install ${lib}`;
20
+ cp.execSync(command, { stdio: "inherit" });
31
21
  }
32
-
33
- //----
34
- // VALIDATIONS
35
- //----
36
- // CHECK OUTPUT
37
- if (command.out === null)
38
- throw new Error(`Output directory is not specified. Add the "--out <output_directory>" option.`);
39
-
40
- // CHECK PARENT DIRECTORY
41
- const parentPath: string = path.resolve(command.out + "/..");
42
- const parentStats: fs.Stats = await fs.promises.stat(parentPath);
22
+ }
43
23
 
44
- if (parentStats.isDirectory() === false)
45
- throw new Error(`Unable to find parent directory of the output path: "${parentPath}".`);
24
+ function sdk(): void
25
+ {
26
+ // PREPARE COMMAND
27
+ const parameters: string[] = [
28
+ "npx ts-node -C ttypescript",
29
+ __dirname + "/../executable/sdk",
30
+ ...process.argv.slice(3)
31
+ ];
32
+ const command: string = parameters.join(" ");
33
+
34
+ // EXECUTE THE COMMAND, BUT IGNORE WARNINGS
35
+ cp.execSync
36
+ (
37
+ command,
38
+ {
39
+ stdio: "inherit",
40
+ env: { "NODE_NO_WARNINGS": "1" }
41
+ }
42
+ );
43
+ }
46
44
 
47
- // CHECK INPUTS
48
- for (const path of input)
45
+ function configure(config: IConfig): boolean
46
+ {
47
+ if (!config.compilerOptions)
49
48
  {
50
- const inputStats: fs.Stats = await fs.promises.stat(path);
51
- if (inputStats.isDirectory() === false)
52
- throw new Error(`Target "${path}" is not a directory.`);
49
+ config.compilerOptions = CompilerOptions.DEFAULT;
50
+ return true;
53
51
  }
52
+ else
53
+ return CompilerOptions.emend(config.compilerOptions);
54
+ }
54
55
 
56
+ async function tsconfig(task: () => void): Promise<void>
57
+ {
55
58
  //----
56
- // GENERATION
59
+ // PREPARE ASSETS
57
60
  //----
58
- if (fs.existsSync("tsconfig.json") === true)
61
+ let prepare: null | (() => Promise<void>) = null;
62
+ let restore: null | (() => Promise<void>) = null;
63
+
64
+ if (fs.existsSync("tsconfig.json") === false)
59
65
  {
66
+ // NO TSCONFIG.JSON
67
+ const config: IConfig = {
68
+ compilerOptions: CompilerOptions.DEFAULT
69
+ };
70
+ prepare = () => fs.promises.writeFile
71
+ (
72
+ "tsconfig.json",
73
+ JSON.stringify(config, null, 2),
74
+ "utf8"
75
+ );
76
+ restore = () => fs.promises.unlink("tsconfig.json")
77
+ }
78
+ else
79
+ {
80
+ // HAS TSCONFIG.JSON
60
81
  const content: string = await fs.promises.readFile("tsconfig.json", "utf8");
61
- const options: tsc.CompilerOptions = JSON.parse(stripJsonComments(content)).compilerOptions;
82
+ const config: IConfig = JSON.parse(stripJsonComments(content));
83
+ const changed: boolean = configure(config);
62
84
 
63
- compilerOptions = compilerOptions
64
- ? { ...options, ...compilerOptions }
65
- : options;
85
+ if (changed === true)
86
+ {
87
+ // NEED TO ADD TRANSFORM PLUGINS
88
+ prepare = () => fs.promises.writeFile
89
+ (
90
+ "tsconfig.json",
91
+ JSON.stringify(config, null, 2),
92
+ "utf8"
93
+ );
94
+ restore = () => fs.promises.writeFile("tsconfig.json", content, "utf8");
95
+ }
66
96
  }
67
97
 
68
- // CHECK NESTIA.CONFIG.TS
98
+ //----
99
+ // EXECUTION
100
+ //----
101
+ // PREPARE SOMETHING
102
+ if (prepare !== null)
103
+ await prepare();
69
104
 
70
- // CALL THE APP.GENERATE()
71
- const app: NestiaApplication = new NestiaApplication({
72
- output: command.out,
73
- input,
74
- compilerOptions,
75
- });
76
- await app.generate();
77
- }
105
+ // EXECUTE THE TASK
106
+ let error: Error | null = null;
107
+ try
108
+ {
109
+ task();
110
+ }
111
+ catch (exp)
112
+ {
113
+ error = exp as Error;
114
+ }
78
115
 
79
- async function install(): Promise<void>
80
- {
81
- await Terminal.execute("npm install --save nestia-fetcher");
116
+ // RESTORE THE TSCONFIG.JSON
117
+ if (restore !== null)
118
+ await restore();
119
+
120
+ // THROW ERROR IF EXISTS
121
+ if (error)
122
+ throw error;
82
123
  }
83
124
 
84
- async function main(): Promise<void>
125
+ async function main()
85
126
  {
86
127
  if (process.argv[2] === "install")
87
128
  await install();
88
129
  else if (process.argv[2] === "sdk")
89
- {
90
- const command: ICommand = cli.parse({
91
- out: ["o", "Output path of the SDK files", "string", null],
92
- });
93
-
94
- try
95
- {
96
- const inputs: string[] = [];
97
- for (const arg of process.argv.slice(3))
98
- {
99
- if (arg[0] === "-")
100
- break;
101
- inputs.push(arg);
102
- }
103
- await sdk(inputs, command);
104
- }
105
- catch (exp)
106
- {
107
- console.log(exp);
108
- process.exit(-1);
109
- }
110
- }
130
+ await tsconfig(sdk);
111
131
  else
112
- {
113
- console.log(`nestia supports only two commands; install and sdk, however you typed ${process.argv[2]}`);
114
- process.exit(-1);
115
- }
132
+ throw new Error(`nestia supports only two commands; install and sdk, however you typed ${process.argv[2]}`);
116
133
  }
117
- main();
134
+ main().catch(exp =>
135
+ {
136
+ console.log(exp.message);
137
+ process.exit(-1);
138
+ });
@@ -0,0 +1,89 @@
1
+ import cli from "cli";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import tsc from "typescript";
5
+ import { Primitive } from "nestia-fetcher";
6
+
7
+ import { IConfiguration } from "../IConfiguration";
8
+ import { NestiaApplication } from "../NestiaApplication";
9
+ import { stripJsonComments } from "../utils/stripJsonComments";
10
+
11
+ interface ICommand
12
+ {
13
+ exclude: string | null;
14
+ out: string | null;
15
+ }
16
+
17
+ async function sdk(include: string[], command: ICommand): Promise<void>
18
+ {
19
+ // CONFIGURATION
20
+ let config: IConfiguration;
21
+ if (fs.existsSync("nestia.config.ts") === true)
22
+ config = Primitive.clone
23
+ (
24
+ await import(path.resolve("nestia.config.ts"))
25
+ );
26
+ else
27
+ {
28
+ if (command.out === null)
29
+ throw new Error(`Output directory is not specified. Add the "--out <output_directory>" option.`);
30
+ config = {
31
+ input: {
32
+ include,
33
+ exclude: command.exclude
34
+ ? [command.exclude]
35
+ : undefined
36
+ },
37
+ output: command.out
38
+ };
39
+ }
40
+
41
+ // VALIDATE OUTPUT DIRECTORY
42
+ const parentPath: string = path.resolve(config.output + "/..");
43
+ const parentStats: fs.Stats = await fs.promises.stat(parentPath);
44
+
45
+ if (parentStats.isDirectory() === false)
46
+ throw new Error(`Unable to find parent directory of the output path: "${parentPath}".`);
47
+
48
+ // GENERATION
49
+ if (fs.existsSync("tsconfig.json") === true)
50
+ {
51
+ const content: string = await fs.promises.readFile("tsconfig.json", "utf8");
52
+ const options: tsc.CompilerOptions = JSON.parse(stripJsonComments(content)).compilerOptions;
53
+
54
+ config.compilerOptions = {
55
+ ...options,
56
+ ...(config.compilerOptions || {})
57
+ };
58
+ }
59
+
60
+ // CALL THE APP.GENERATE()
61
+ const app: NestiaApplication = new NestiaApplication(config);
62
+ await app.generate();
63
+ }
64
+
65
+ async function main(): Promise<void>
66
+ {
67
+ const command: ICommand = cli.parse({
68
+ exclude: ["e", "Something to exclude", "string", null],
69
+ out: ["o", "Output path of the SDK files", "string", null],
70
+ });
71
+
72
+ try
73
+ {
74
+ const inputs: string[] = [];
75
+ for (const arg of process.argv.slice(2))
76
+ {
77
+ if (arg[0] === "-")
78
+ break;
79
+ inputs.push(arg);
80
+ }
81
+ await sdk(inputs, command);
82
+ }
83
+ catch (exp)
84
+ {
85
+ console.log(exp);
86
+ process.exit(-1);
87
+ }
88
+ }
89
+ main();
@@ -1,5 +1,6 @@
1
- import * as fs from "fs";
1
+ import fs from "fs";
2
2
  import { HashMap } from "tstl/container/HashMap";
3
+ import { IConfiguration } from "../IConfiguration";
3
4
 
4
5
  import { IRoute } from "../structures/IRoute";
5
6
  import { ImportDictionary } from "../utils/ImportDictionary";
@@ -10,7 +11,7 @@ export namespace FileGenerator
10
11
  /* ---------------------------------------------------------
11
12
  CONSTRUCTOR
12
13
  --------------------------------------------------------- */
13
- export async function generate(outDir: string, routeList: IRoute[]): Promise<void>
14
+ export async function generate(config: IConfiguration, routeList: IRoute[]): Promise<void>
14
15
  {
15
16
  // CONSTRUCT FOLDER TREE
16
17
  const root: Directory = new Directory(null, "functional");
@@ -21,7 +22,7 @@ export namespace FileGenerator
21
22
  relocate(root);
22
23
 
23
24
  // ITERATE FILES
24
- await iterate(outDir + "/functional", root);
25
+ await iterate(!!config.assert, config.output + "/functional", root);
25
26
  }
26
27
 
27
28
  function emplace(directory: Directory, route: IRoute): void
@@ -63,7 +64,12 @@ export namespace FileGenerator
63
64
  /* ---------------------------------------------------------
64
65
  FILE ITERATOR
65
66
  --------------------------------------------------------- */
66
- async function iterate(outDir: string, directory: Directory): Promise<void>
67
+ async function iterate
68
+ (
69
+ assert: boolean,
70
+ outDir: string,
71
+ directory: Directory
72
+ ): Promise<void>
67
73
  {
68
74
  // CREATE A NEW DIRECTORY
69
75
  try
@@ -76,7 +82,7 @@ export namespace FileGenerator
76
82
  let content: string = "";
77
83
  for (const it of directory.directories)
78
84
  {
79
- await iterate(`${outDir}/${it.first}`, it.second);
85
+ await iterate(assert, `${outDir}/${it.first}`, it.second);
80
86
  content += `export * as ${it.first} from "./${it.first}";\n`;
81
87
  }
82
88
  content += "\n";
@@ -88,27 +94,34 @@ export namespace FileGenerator
88
94
  for (const tuple of route.imports)
89
95
  for (const instance of tuple[1])
90
96
  importDict.emplace(tuple[0], false, instance);
91
- content += FunctionGenerator.generate(route) + "\n\n";
97
+ content += FunctionGenerator.generate(assert, route) + "\n\n";
92
98
  }
93
99
 
94
100
  // FINALIZE THE CONTENT
95
101
  if (directory.routes.length !== 0)
102
+ {
103
+ const primitived: boolean = directory.routes.some(route => route.output !== "void"
104
+ || route.parameters.some(param => param.category !== "param")
105
+ );
106
+ const asserted: boolean = assert
107
+ && directory.routes.some(route => route.parameters.length !== 0);
108
+
109
+ const fetcher: string[] = ["Fetcher"];
110
+ if (primitived)
111
+ fetcher.push("Primitive");
112
+
96
113
  content = ""
97
- + `import { AesPkcs5, Fetcher, Primitive } from "nestia-fetcher";\n`
114
+ + `import { ${fetcher.join(", ")} } from "nestia-fetcher";\n`
98
115
  + `import type { IConnection } from "nestia-fetcher";\n`
116
+ + (asserted ? `import { assertType } from "typescript-is";\n` : "")
99
117
  +
100
118
  (
101
119
  importDict.empty()
102
120
  ? ""
103
121
  : "\n" + importDict.toScript(outDir) + "\n"
104
122
  )
105
- + content + "\n\n"
106
- + "//---------------------------------------------------------\n"
107
- + "// TO PREVENT THE UNUSED VARIABLE ERROR\n"
108
- + "//---------------------------------------------------------\n"
109
- + "AesPkcs5;\n"
110
- + "Fetcher;\n"
111
- + "Primitive;";
123
+ + content;
124
+ }
112
125
 
113
126
  content = "/**\n"
114
127
  + " * @packageDocumentation\n"
@@ -1,4 +1,4 @@
1
- import type * as tsc from "typescript";
1
+ import type tsc from "typescript";
2
2
  import { Pair } from "tstl/utility/Pair";
3
3
  import { Vector } from "tstl/container/Vector";
4
4
 
@@ -6,13 +6,13 @@ import { IRoute } from "../structures/IRoute";
6
6
 
7
7
  export namespace FunctionGenerator
8
8
  {
9
- export function generate(route: IRoute): string
9
+ export function generate(assert: boolean, route: IRoute): string
10
10
  {
11
11
  const query: IRoute.IParameter | undefined = route.parameters.find(param => param.category === "query");
12
12
  const input: IRoute.IParameter | undefined = route.parameters.find(param => param.category === "body");
13
13
 
14
14
  return [head, body, tail]
15
- .map(closure => closure(route, query, input))
15
+ .map(closure => closure(route, query, input, assert))
16
16
  .filter(str => !!str)
17
17
  .join("\n");
18
18
  }
@@ -20,7 +20,13 @@ export namespace FunctionGenerator
20
20
  /* ---------------------------------------------------------
21
21
  BODY
22
22
  --------------------------------------------------------- */
23
- function body(route: IRoute, query: IRoute.IParameter | undefined, input: IRoute.IParameter | undefined): string
23
+ function body
24
+ (
25
+ route: IRoute,
26
+ query: IRoute.IParameter | undefined,
27
+ input: IRoute.IParameter | undefined,
28
+ assert: boolean
29
+ ): string
24
30
  {
25
31
  // FETCH ARGUMENTS WITH REQUST BODY
26
32
  const parameters = filter_parameters(route, query);
@@ -34,8 +40,15 @@ export namespace FunctionGenerator
34
40
  if (input !== undefined)
35
41
  fetchArguments.push(input.name);
36
42
 
43
+ const assertions: string = assert === true && route.parameters.length !== 0
44
+ ? route.parameters
45
+ .map(param => ` assertType<typeof ${param.name}>(${param.name});`)
46
+ .join("\n") + "\n\n"
47
+ : "";
48
+
37
49
  // RETURNS WITH FINALIZATION
38
50
  return "{\n"
51
+ + assertions
39
52
  + " return Fetcher.fetch\n"
40
53
  + " (\n"
41
54
  + fetchArguments.map(param => ` ${param}`).join(",\n") + "\n"
@@ -54,7 +67,12 @@ export namespace FunctionGenerator
54
67
  /* ---------------------------------------------------------
55
68
  HEAD & TAIL
56
69
  --------------------------------------------------------- */
57
- function head(route: IRoute, query: IRoute.IParameter | undefined, input: IRoute.IParameter | undefined): string
70
+ function head
71
+ (
72
+ route: IRoute,
73
+ query: IRoute.IParameter | undefined,
74
+ input: IRoute.IParameter | undefined
75
+ ): string
58
76
  {
59
77
  //----
60
78
  // CONSTRUCT COMMENT
@@ -132,7 +150,12 @@ export namespace FunctionGenerator
132
150
  + ` ): Promise<${output}>`;
133
151
  }
134
152
 
135
- function tail(route: IRoute, query: IRoute.IParameter | undefined, input: IRoute.IParameter | undefined): string | null
153
+ function tail
154
+ (
155
+ route: IRoute,
156
+ query: IRoute.IParameter | undefined,
157
+ input: IRoute.IParameter | undefined
158
+ ): string | null
136
159
  {
137
160
  // LIST UP TYPES
138
161
  const types: Pair<string, string>[] = [];
@@ -1,15 +1,20 @@
1
- import * as fs from "fs";
1
+ import fs from "fs";
2
2
  import { DirectoryUtil } from "../utils/DirectoryUtil";
3
3
 
4
4
  import { IRoute } from "../structures/IRoute";
5
5
  import { FileGenerator } from "./FileGenerator";
6
+ import { IConfiguration } from "../IConfiguration";
6
7
 
7
8
  export namespace SdkGenerator
8
9
  {
9
- export async function generate(outDir: string, routeList: IRoute[]): Promise<void>
10
+ export async function generate
11
+ (
12
+ config: IConfiguration,
13
+ routeList: IRoute[],
14
+ ): Promise<void>
10
15
  {
11
16
  // PREPARE NEW DIRECTORIES
12
- try { await fs.promises.mkdir(outDir); } catch {}
17
+ try { await fs.promises.mkdir(config.output); } catch {}
13
18
 
14
19
  // BUNDLING
15
20
  const bundle: string[] = await fs.promises.readdir(BUNDLE);
@@ -21,13 +26,13 @@ export namespace SdkGenerator
21
26
  if (stats.isFile() === true)
22
27
  {
23
28
  const content: string = await fs.promises.readFile(current, "utf8");
24
- await fs.promises.writeFile(`${outDir}/${file}`, content, "utf8");
29
+ await fs.promises.writeFile(`${config.output}/${file}`, content, "utf8");
25
30
  }
26
31
  }
27
- await DirectoryUtil.copy(BUNDLE + "/__internal", outDir + "/__internal");
32
+ await DirectoryUtil.copy(BUNDLE + "/__internal", config.output + "/__internal");
28
33
 
29
34
  // FUNCTIONAL
30
- await FileGenerator.generate(outDir, routeList);
35
+ await FileGenerator.generate(config, routeList);
31
36
  }
32
37
  }
33
38
 
@@ -0,0 +1,113 @@
1
+ export interface CompilerOptions
2
+ {
3
+ target: string;
4
+ module: string;
5
+ lib: string[];
6
+ strict: boolean;
7
+ downlevelIteration: boolean;
8
+ esModuleInterop?: boolean;
9
+ plugins?: Array<{
10
+ transform?: string
11
+ }>;
12
+ types?: string[];
13
+ experimentalDecorators?: boolean;
14
+ emitDecoratorMetadata?: boolean;
15
+ }
16
+ export namespace CompilerOptions
17
+ {
18
+ export const DEPENDENCIES: string[] = [
19
+ "nestia-fetcher",
20
+ "typescript-is"
21
+ ];
22
+
23
+ export const TRANSFORMERS: string[] = [
24
+ "typescript-is/lib/transform-inline/transformer",
25
+ "typescript-transform-paths"
26
+ ];
27
+
28
+ export const TYPES: string[] = []
29
+
30
+ export const DEFAULT = {
31
+ target: "es5",
32
+ module: "commonjs",
33
+ lib: [
34
+ "DOM",
35
+ "ES2015"
36
+ ],
37
+ strict: true,
38
+ downlevelIteration: true,
39
+ esModuleInterop: true,
40
+ plugins: TRANSFORMERS.map(transform => ({ transform })),
41
+ types: [
42
+ "node",
43
+ "reflect-metadata"
44
+ ],
45
+ experimentalDecorators: true,
46
+ emitDecoratorMetadata: true,
47
+ };
48
+
49
+ export function emend(options: CompilerOptions): boolean
50
+ {
51
+ // FILL ARRAY DATA
52
+ if (!options.plugins)
53
+ options.plugins = [];
54
+ if (!options.types)
55
+ options.types = [];
56
+
57
+ // CONSTRUCT CHECKERS
58
+ const emended: Required<CompilerOptions> = options as Required<CompilerOptions>;
59
+ const checkers: Array<() => boolean> = [
60
+ () =>
61
+ {
62
+ let changed: boolean = false;
63
+ for (const transform of CompilerOptions.TRANSFORMERS)
64
+ {
65
+ if (emended.plugins.find(elem => elem.transform === transform) !== undefined)
66
+ continue;
67
+
68
+ changed = true;
69
+ emended.plugins.push({ transform });
70
+ }
71
+ return changed;
72
+ },
73
+ () =>
74
+ {
75
+ let changed: boolean = false;
76
+ for (const type of CompilerOptions.TYPES)
77
+ {
78
+ if (emended.types.find(elem => elem === type) !== undefined)
79
+ continue;
80
+
81
+ changed = true;
82
+ emended.types.push(type);
83
+ }
84
+ return changed;
85
+ },
86
+ () =>
87
+ {
88
+ const changed: boolean = emended.experimentalDecorators !== true;
89
+ if (changed)
90
+ emended.experimentalDecorators = true;
91
+ return changed;
92
+ },
93
+ () =>
94
+ {
95
+ const changed: boolean = emended.emitDecoratorMetadata !== true;
96
+ if (changed)
97
+ emended.emitDecoratorMetadata = true;
98
+ return changed;
99
+ },
100
+ () =>
101
+ {
102
+ const changed: boolean = emended.esModuleInterop !== true;
103
+ if (changed)
104
+ emended.esModuleInterop = true;
105
+ return changed;
106
+ }
107
+ ];
108
+
109
+ // DO CHECK IT
110
+ const checks: boolean[] = checkers.map(func => func());
111
+ return checks.some(flag => flag);
112
+ }
113
+ }
@@ -1,4 +1,4 @@
1
- import type * as tsc from "typescript";
1
+ import type tsc from "typescript";
2
2
  import { ParamCategory } from "./ParamCategory";
3
3
 
4
4
  export interface IRoute
@@ -1,5 +1,5 @@
1
1
  import del from "del";
2
- import * as fs from "fs";
2
+ import fs from "fs";
3
3
 
4
4
  export namespace DirectoryUtil
5
5
  {
@@ -1,4 +1,4 @@
1
- import * as path from "path";
1
+ import path from "path";
2
2
 
3
3
  import { HashMap } from "tstl/container/HashMap";
4
4
  import { HashSet } from "tstl/container/HashSet";
package/tsconfig.json CHANGED
@@ -72,12 +72,11 @@
72
72
 
73
73
  /* Advanced Options */
74
74
  "skipLibCheck": true, /* Skip type checking of declaration files. */
75
- "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
75
+ "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
76
+ "plugins": [
77
+ { "transform": "typescript-is/lib/transform-inline/transformer" },
78
+ { "transform": "typescript-transform-paths" }
79
+ ]
76
80
  },
77
- "include": [
78
- "src",
79
- "test/default/src",
80
- "test/nestia.config.ts/src",
81
- // "test/tsconfig.json/src"
82
- ]
81
+ "include": ["src"]
83
82
  }
@@ -1,19 +0,0 @@
1
- import * as cp from "child_process";
2
- import { Pair } from "tstl/utility/Pair";
3
-
4
- export namespace Terminal
5
- {
6
- export function execute(...commands: string[]): Promise<Pair<string, string>>
7
- {
8
- return new Promise((resolve, reject) =>
9
- {
10
- cp.exec(commands.join(" && "), (error: Error | null, stdout: string, stderr: string) =>
11
- {
12
- if (error)
13
- reject(error);
14
- else
15
- resolve(new Pair(stdout, stderr));
16
- });
17
- });
18
- }
19
- }