nestia 0.2.2 → 0.3.1
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.
- package/README.md +222 -15
- package/package.json +3 -3
- package/src/NestiaApplication.ts +29 -19
- package/src/bin/nestia.ts +48 -14
- package/src/generates/FileGenerator.ts +4 -1
- package/src/generates/FunctionGenerator.ts +8 -8
- package/src/utils/stripJsonComments.ts +79 -0
- package/tsconfig.json +1 -1
package/README.md
CHANGED
|
@@ -6,21 +6,42 @@ Automatic SDK generator for the NestJS.
|
|
|
6
6
|
[](https://www.npmjs.com/package/nestia)
|
|
7
7
|
[](https://github.com/samchon/nestia/actions?query=workflow%3Abuild)
|
|
8
8
|
|
|
9
|
-
## Outline
|
|
10
|
-
If you're making a backend server with the **TypeScript** and **NestJS**, you don't need any extra dedication, for delivering Rest API to the client (front-end) developers, like writing `swagger.json` comments.
|
|
11
|
-
|
|
12
|
-
Just generate a SDK library through the **Nestia** and deliver the SDK library to the client developers. The client developers can call your backend server API just by calling the SDK library functions with *await* symbol, re-using the interfaces what you've defined.
|
|
13
|
-
|
|
14
9
|
```bash
|
|
15
10
|
npm install --save-dev nestia
|
|
16
11
|
npx nestia sdk "src/controller" --out "src/api"
|
|
17
12
|
```
|
|
18
13
|
|
|
19
|
-
|
|
14
|
+
Don't write any `swagger` comment. Just deliver the SDK.
|
|
15
|
+
|
|
16
|
+
When you're developing a backend server using the `NestJS`, you don't need any extra dedication, for delivering the Rest API to the client developers, like writing the `swagger` comments. You just run this **Nestia** up, then **Nestia** would generate the SDK automatically, by analyzing your controller classes in the compliation and runtime level.
|
|
17
|
+
|
|
18
|
+
With the automatically generated SDK through this **Nestia**, client developer also does not need any extra work, like reading `swagger` and writing the duplicated interaction code. Client developer only needs to import the SDK and calls matched function with the `await` symbol.
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
```typescript
|
|
21
|
+
import api from "@samchon/bbs-api";
|
|
22
|
+
import { IBbsArticle } from "@samchon/bbs-api/lib/structures/bbs/IBbsArticle";
|
|
23
|
+
import { IPage } from "@samchon/bbs-api/lib/structures/common/IPage";
|
|
24
|
+
|
|
25
|
+
export async function test_article_read(connection: api.IConnection): Promise<void>
|
|
26
|
+
{
|
|
27
|
+
// LIST UP ARTICLE SUMMARIES
|
|
28
|
+
const index: IPage<IBbsArticle.ISummary> = await api.functional.bbs.articles.index
|
|
29
|
+
(
|
|
30
|
+
connection,
|
|
31
|
+
"free",
|
|
32
|
+
{ limit: 100, page: 1 }
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// READ AN ARTICLE DETAILY
|
|
36
|
+
const article: IBbsArticle = await api.functional.bbs.articles.at
|
|
37
|
+
(
|
|
38
|
+
connection,
|
|
39
|
+
"free",
|
|
40
|
+
index.data[0].id
|
|
41
|
+
);
|
|
42
|
+
console.log(article.title, aritlce.body, article.files);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
24
45
|
|
|
25
46
|
|
|
26
47
|
|
|
@@ -29,29 +50,215 @@ If you want to see an example project using the **Nestia**, click below links:
|
|
|
29
50
|
### Installation
|
|
30
51
|
```bash
|
|
31
52
|
npm install --save-dev nestia
|
|
32
|
-
npx nestia sdk "src/controllers" --out "src/api"
|
|
33
53
|
```
|
|
34
54
|
|
|
35
55
|
Installing the **Nestia** is very easy.
|
|
36
56
|
|
|
37
57
|
Just type the `npm install --save-dev nestia` command in your NestJS backend project.
|
|
38
58
|
|
|
39
|
-
###
|
|
59
|
+
### SDK generation
|
|
40
60
|
```bash
|
|
41
61
|
npx nestia sdk <source_controller_directory> --out <output_sdk_directory>
|
|
42
62
|
|
|
43
63
|
npx nestia sdk "src/controllers" --out "src/api"
|
|
44
|
-
npx nestia sdk "src/consumers
|
|
64
|
+
npx nestia sdk "src/controllers/consumers" "src/controllers/sellers" --out "src/api
|
|
45
65
|
```
|
|
46
66
|
|
|
47
|
-
To generate a SDK library through the **Nestia** is very easy.
|
|
67
|
+
To generate a SDK library through the **Nestia** is very easy.
|
|
68
|
+
|
|
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.
|
|
48
70
|
|
|
71
|
+
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).
|
|
49
72
|
|
|
50
73
|
```bash
|
|
51
74
|
npx nestia install
|
|
52
75
|
```
|
|
53
76
|
|
|
54
|
-
|
|
77
|
+
### Dependencies
|
|
78
|
+
SDK library generated by the **Nestia** has some dependencies like below.
|
|
79
|
+
|
|
80
|
+
When you type the `nestia install` command in the console, those dependencies would be automatically installed and enrolled to the `dependencies` and `devDependencies` fields in the `package.json`
|
|
55
81
|
|
|
56
82
|
- [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node)
|
|
57
|
-
- [node-fetch](https://github.com/node-fetch/node-fetch)
|
|
83
|
+
- [node-fetch](https://github.com/node-fetch/node-fetch)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
## Advanced
|
|
89
|
+
### `nestia.config.ts`
|
|
90
|
+
```typescript
|
|
91
|
+
export namespace NestiaApplication
|
|
92
|
+
{
|
|
93
|
+
export interface IConfiguration
|
|
94
|
+
{
|
|
95
|
+
/**
|
|
96
|
+
* List of directories containing the NestJS controller classes.
|
|
97
|
+
*/
|
|
98
|
+
input: string | string[];
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Output directory that SDK would be placed in.
|
|
102
|
+
*/
|
|
103
|
+
output: string;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Compiler options for the TypeScript.
|
|
107
|
+
*
|
|
108
|
+
* If omitted, the configuration would follow the `tsconfig.json`.
|
|
109
|
+
*/
|
|
110
|
+
compilerOptions?: tsc.CompilerOptions
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Instead of specifying `input` and `output` directories using the cli options, you can specify those directories as an independent configuration file. It's the `nestia.config.ts` and with the `nestia.config.ts` file, you also configure independent TypeScript compiler option from the `tsconfig.json`.
|
|
116
|
+
|
|
117
|
+
Write below content as the `nestia.config.ts` file and place it onto the root directory of your backend project. After the configuration, you can generate the SDK only with the `npx nestia sdk` command, without any directory specification.
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
export = {
|
|
121
|
+
input: "src/controllers`",
|
|
122
|
+
output: "src/api"
|
|
123
|
+
};
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
### Recommended Structures
|
|
129
|
+
When developing a NestJS backend server with this **Nestia**, I recommend you to follow below directory structure. The key princinple of below structure is to gathering all of the DTO interface structures into the `src/api/structures` directory and gather all of the controller classes into the `src/controllers` directory.
|
|
130
|
+
|
|
131
|
+
If you place the SDK onto the `src/api` directory and gather all of the DTO interface structures into the `src/api/structures` directory, you can publish the SDK library very easily without any special configuration. Also when you're develop the test automation program, you can implement the API testing features very convenienty through the automatically generated SDK through this **Nestia**.
|
|
132
|
+
|
|
133
|
+
- src
|
|
134
|
+
- api
|
|
135
|
+
- **functional**: automatically generated SDK functions
|
|
136
|
+
- **structures**: DTO structures
|
|
137
|
+
- controllers
|
|
138
|
+
- providers
|
|
139
|
+
- models
|
|
140
|
+
- **test**: Test automation program using SDK functions
|
|
141
|
+
- package.json
|
|
142
|
+
- tsconfig.json
|
|
143
|
+
- nestia.config.ts
|
|
144
|
+
|
|
145
|
+
For your deep understanding about this directory structure with this **Nestia**, I've prepared an example backend project. Looking around the example repository and reading the [README.md](https://github.com/samchon/backend#13-directories) of it, you can feel that such directory structure is how convenient for SDK publishing and test automation program implementation.
|
|
146
|
+
|
|
147
|
+
- https://github.com/samchon/backend
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
## Demonstration
|
|
153
|
+
To demonstrate which SDK codes would be generated by this **Nestia**:
|
|
154
|
+
|
|
155
|
+
- [Controllers of the NestJS](https://github.surf/samchon/nestia/blob/HEAD/test/default/src/controllers/base/SaleCommentsController.ts)
|
|
156
|
+
- [Structures used in the RestAPI](https://github.surf/samchon/nestia/blob/HEAD/test/default/src/api/structures/sales/articles/ISaleArticle.ts)
|
|
157
|
+
- [SDK generated by this **Nestia**](https://github.surf/samchon/nestia/blob/HEAD/test/default/src/api/functional/consumers/sales/reviews/index.ts)
|
|
158
|
+
|
|
159
|
+
### Controller
|
|
160
|
+
If you've decided to adapt this **Nestia** and you want to generate the SDK directly, you don't need any extra work. Just keep you controller class down and do noting. The only one exceptional case that you need an extra dedication is, when you want to explain about the API function to the client developers through the comments.
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
@nest.Controller("consumers/:section/sales/:saleId/questions")
|
|
164
|
+
export class ConsumerSaleQuestionsController
|
|
165
|
+
{
|
|
166
|
+
/**
|
|
167
|
+
* Store a new question.
|
|
168
|
+
*
|
|
169
|
+
* @param request Instance of the Express.Request
|
|
170
|
+
* @param section Code of the target section
|
|
171
|
+
* @param saleId ID of the target sale
|
|
172
|
+
* @param input Content to archive
|
|
173
|
+
*
|
|
174
|
+
* @return Newly archived question
|
|
175
|
+
* @throw 400 bad request error when type of the input data is not valid
|
|
176
|
+
* @throw 401 unauthorized error when you've not logged in yet
|
|
177
|
+
*/
|
|
178
|
+
@nest.Post()
|
|
179
|
+
public store
|
|
180
|
+
(
|
|
181
|
+
@nest.Request() request: express.Request,
|
|
182
|
+
@nest.Param("section") section: string,
|
|
183
|
+
@nest.Param("saleId") saleId: number,
|
|
184
|
+
@nest.Body() input: ISaleQuestion.IStore
|
|
185
|
+
): Promise<ISaleQuestion>;
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### SDK
|
|
190
|
+
When you run the **Nestia** up using the upper controller class `ConsumerSaleQuestionsController`, the **Nestia** would generate below function for the client developers, by analyzing the `ConsumerSaleQuestionsController` class in the compilation and runtime level.
|
|
191
|
+
|
|
192
|
+
As you can see, the comments from the `ConsumerSaleQuestionsController.store()` are fully copied to the SDK function. Therefore, if you want to deliver detailed description about the API function, writing the detailed comment would be tne best choice.
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
/**
|
|
196
|
+
* Store a new question.
|
|
197
|
+
*
|
|
198
|
+
* @param connection connection Information of the remote HTTP(s) server with headers (+encryption password)
|
|
199
|
+
* @param request Instance of the Express.Request
|
|
200
|
+
* @param section Code of the target section
|
|
201
|
+
* @param saleId ID of the target sale
|
|
202
|
+
* @param input Content to archive
|
|
203
|
+
* @return Newly archived question
|
|
204
|
+
* @throw 400 bad request error when type of the input data is not valid
|
|
205
|
+
* @throw 401 unauthorized error when you've not logged in yet
|
|
206
|
+
*
|
|
207
|
+
* @nestia Generated by Nestia - https://github.com/samchon/nestia
|
|
208
|
+
* @controller ConsumerSaleQuestionsController.store()
|
|
209
|
+
* @path POST /consumers/:section/sales/:saleId/questions/
|
|
210
|
+
*/
|
|
211
|
+
export function store
|
|
212
|
+
(
|
|
213
|
+
connection: IConnection,
|
|
214
|
+
section: string,
|
|
215
|
+
saleId: number,
|
|
216
|
+
input: store.Input
|
|
217
|
+
): Promise<store.Output>
|
|
218
|
+
{
|
|
219
|
+
return Fetcher.fetch
|
|
220
|
+
(
|
|
221
|
+
connection,
|
|
222
|
+
{
|
|
223
|
+
input_encrypted: false,
|
|
224
|
+
output_encrypted: false
|
|
225
|
+
},
|
|
226
|
+
"POST",
|
|
227
|
+
`/consumers/${section}/sales/${saleId}/questions/`,
|
|
228
|
+
input
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
export namespace store
|
|
232
|
+
{
|
|
233
|
+
export type Input = Primitive<ISaleInquiry.IStore>;
|
|
234
|
+
export type Output = Primitive<ISaleInquiry<ISaleArticle.IContent>>;
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
## Appendix
|
|
242
|
+
### Safe-TypeORM
|
|
243
|
+
https://github.com/samchon/safe-typeorm
|
|
244
|
+
|
|
245
|
+
[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.
|
|
246
|
+
|
|
247
|
+
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.
|
|
248
|
+
|
|
249
|
+
### Technial Support
|
|
250
|
+
samchon.github@gmail.com
|
|
251
|
+
|
|
252
|
+
I can provide technical support about those **Nestia** and [safe-typeorm](https://github.com/samchon/safe-typeorm).
|
|
253
|
+
|
|
254
|
+
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.
|
|
255
|
+
|
|
256
|
+
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.
|
|
257
|
+
|
|
258
|
+
### Archidraw
|
|
259
|
+
https://www.archisketch.com/
|
|
260
|
+
|
|
261
|
+
I have special thanks to the Archidraw, where I'm working for.
|
|
262
|
+
|
|
263
|
+
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.
|
|
264
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nestia",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Automatic SDK and Document generator for the NestJS",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"dev": "tsc --watch",
|
|
11
|
-
"test": "
|
|
11
|
+
"test": "cd test && bash script.sh"
|
|
12
12
|
},
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"cli": "^1.0.1",
|
|
35
35
|
"del": "^6.0.0",
|
|
36
36
|
"ts-node": "^9.1.1",
|
|
37
|
-
"tstl": "^2.
|
|
37
|
+
"tstl": "^2.5.0",
|
|
38
38
|
"ttypescript": "^1.5.12",
|
|
39
39
|
"typescript": "^4.3.2"
|
|
40
40
|
},
|
package/src/NestiaApplication.ts
CHANGED
|
@@ -15,26 +15,18 @@ import { ArrayUtil } from "./utils/ArrayUtil";
|
|
|
15
15
|
|
|
16
16
|
export class NestiaApplication
|
|
17
17
|
{
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
private readonly config_: NestiaApplication.IConfiguration;
|
|
19
|
+
private readonly bundle_checker_: Singleton<Promise<(str: string) => boolean>>;
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
public constructor
|
|
24
|
-
(
|
|
25
|
-
inputs: string[],
|
|
26
|
-
output: string
|
|
27
|
-
)
|
|
21
|
+
public constructor(config: NestiaApplication.IConfiguration)
|
|
28
22
|
{
|
|
29
|
-
this.
|
|
30
|
-
this.output = path.resolve(output);
|
|
31
|
-
|
|
23
|
+
this.config_ = config;
|
|
32
24
|
this.bundle_checker_ = new Singleton(async () =>
|
|
33
25
|
{
|
|
34
26
|
const bundles: string[] = await fs.promises.readdir(`${__dirname}${path.sep}bundle`);
|
|
35
27
|
const tuples: Pair<string, boolean>[] = await ArrayUtil.asyncMap(bundles, async file =>
|
|
36
28
|
{
|
|
37
|
-
const relative: string = `${this.output}${path.sep}${file}`;
|
|
29
|
+
const relative: string = `${this.config_.output}${path.sep}${file}`;
|
|
38
30
|
const stats: fs.Stats = await fs.promises.stat(`${__dirname}${path.sep}bundle${path.sep}${file}`);
|
|
39
31
|
|
|
40
32
|
return new Pair(relative, stats.isDirectory());
|
|
@@ -52,13 +44,17 @@ export class NestiaApplication
|
|
|
52
44
|
});
|
|
53
45
|
}
|
|
54
46
|
|
|
55
|
-
public async
|
|
47
|
+
public async generate(): Promise<void>
|
|
56
48
|
{
|
|
57
49
|
// LOAD CONTROLLER FILES
|
|
58
50
|
const fileList: string[] = [];
|
|
59
|
-
|
|
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)))
|
|
60
56
|
{
|
|
61
|
-
const found: string[] = await SourceFinder.find(
|
|
57
|
+
const found: string[] = await SourceFinder.find(file);
|
|
62
58
|
const filtered: string[] = await ArrayUtil.asyncFilter(found, file => this.is_not_excluded(file));
|
|
63
59
|
|
|
64
60
|
fileList.push(...filtered);
|
|
@@ -72,7 +68,11 @@ export class NestiaApplication
|
|
|
72
68
|
controllerList.push(...await ReflectAnalyzer.analyze(unique, file));
|
|
73
69
|
|
|
74
70
|
// ANALYZE TYPESCRIPT CODE
|
|
75
|
-
const program: tsc.Program = tsc.createProgram
|
|
71
|
+
const program: tsc.Program = tsc.createProgram
|
|
72
|
+
(
|
|
73
|
+
controllerList.map(c => c.file),
|
|
74
|
+
this.config_.compilerOptions || {}
|
|
75
|
+
);
|
|
76
76
|
const checker: tsc.TypeChecker = program.getTypeChecker();
|
|
77
77
|
|
|
78
78
|
const routeList: IRoute[] = [];
|
|
@@ -86,12 +86,22 @@ export class NestiaApplication
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
// DO GENERATE
|
|
89
|
-
await SdkGenerator.generate(this.output, routeList);
|
|
89
|
+
await SdkGenerator.generate(this.config_.output, routeList);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
private async is_not_excluded(file: string): Promise<boolean>
|
|
93
93
|
{
|
|
94
|
-
return file.indexOf(`${this.output}${path.sep}functional`) === -1
|
|
94
|
+
return file.indexOf(`${this.config_.output}${path.sep}functional`) === -1
|
|
95
95
|
&& (await this.bundle_checker_.get())(file) === false;
|
|
96
96
|
}
|
|
97
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
|
+
}
|
package/src/bin/nestia.ts
CHANGED
|
@@ -3,42 +3,77 @@
|
|
|
3
3
|
import cli from "cli";
|
|
4
4
|
import fs from "fs";
|
|
5
5
|
import path from "path";
|
|
6
|
-
|
|
7
|
-
import { Terminal } from "../utils/Terminal";
|
|
6
|
+
import tsc from "typescript";
|
|
8
7
|
|
|
9
8
|
import { NestiaApplication } from "../NestiaApplication";
|
|
10
9
|
|
|
10
|
+
import { Terminal } from "../utils/Terminal";
|
|
11
|
+
import { stripJsonComments } from "../utils/stripJsonComments";
|
|
12
|
+
|
|
11
13
|
interface ICommand
|
|
12
14
|
{
|
|
13
|
-
install: boolean;
|
|
14
15
|
out: string | null;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
async function sdk(
|
|
18
|
+
async function sdk(input: string[], command: ICommand): Promise<void>
|
|
18
19
|
{
|
|
19
|
-
|
|
20
|
+
let compilerOptions: tsc.CompilerOptions | undefined = {};
|
|
21
|
+
|
|
22
|
+
//----
|
|
23
|
+
// NESTIA.CONFIG.TS
|
|
24
|
+
//----
|
|
25
|
+
if (fs.existsSync("nestia.config.ts") === true)
|
|
26
|
+
{
|
|
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;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//----
|
|
34
|
+
// VALIDATIONS
|
|
35
|
+
//----
|
|
36
|
+
// CHECK OUTPUT
|
|
20
37
|
if (command.out === null)
|
|
21
38
|
throw new Error(`Output directory is not specified. Add the "--out <output_directory>" option.`);
|
|
22
39
|
|
|
40
|
+
// CHECK PARENT DIRECTORY
|
|
23
41
|
const parentPath: string = path.resolve(command.out + "/..");
|
|
24
42
|
const parentStats: fs.Stats = await fs.promises.stat(parentPath);
|
|
43
|
+
|
|
25
44
|
if (parentStats.isDirectory() === false)
|
|
26
45
|
throw new Error(`Unable to find parent directory of the output path: "${parentPath}".`);
|
|
27
46
|
|
|
28
|
-
//
|
|
29
|
-
for (const
|
|
47
|
+
// CHECK INPUTS
|
|
48
|
+
for (const path of input)
|
|
30
49
|
{
|
|
31
|
-
const inputStats: fs.Stats = await fs.promises.stat(
|
|
50
|
+
const inputStats: fs.Stats = await fs.promises.stat(path);
|
|
32
51
|
if (inputStats.isDirectory() === false)
|
|
33
|
-
throw new Error(`Target "${
|
|
52
|
+
throw new Error(`Target "${path}" is not a directory.`);
|
|
34
53
|
}
|
|
35
54
|
|
|
36
55
|
//----
|
|
37
|
-
//
|
|
56
|
+
// GENERATION
|
|
38
57
|
//----
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
58
|
+
if (fs.existsSync("tsconfig.json") === true)
|
|
59
|
+
{
|
|
60
|
+
const content: string = await fs.promises.readFile("tsconfig.json", "utf8");
|
|
61
|
+
const options: tsc.CompilerOptions = JSON.parse(stripJsonComments(content)).compilerOptions;
|
|
62
|
+
|
|
63
|
+
compilerOptions = compilerOptions
|
|
64
|
+
? { ...options, ...compilerOptions }
|
|
65
|
+
: options;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// CHECK NESTIA.CONFIG.TS
|
|
69
|
+
|
|
70
|
+
// CALL THE APP.GENERATE()
|
|
71
|
+
const app: NestiaApplication = new NestiaApplication({
|
|
72
|
+
output: command.out,
|
|
73
|
+
input,
|
|
74
|
+
compilerOptions,
|
|
75
|
+
});
|
|
76
|
+
await app.generate();
|
|
42
77
|
}
|
|
43
78
|
|
|
44
79
|
async function install(): Promise<void>
|
|
@@ -58,7 +93,6 @@ async function main(): Promise<void>
|
|
|
58
93
|
{
|
|
59
94
|
const command: ICommand = cli.parse({
|
|
60
95
|
out: ["o", "Output path of the SDK files", "string", null],
|
|
61
|
-
install: ["i", "Install Dependencies", "boolean", false]
|
|
62
96
|
});
|
|
63
97
|
|
|
64
98
|
try
|
|
@@ -34,7 +34,10 @@ export namespace FileGenerator
|
|
|
34
34
|
function emplace(directory: Directory, route: IRoute): void
|
|
35
35
|
{
|
|
36
36
|
// SEPARATE IDENTIFIERS
|
|
37
|
-
const identifiers: string[] = route.path
|
|
37
|
+
const identifiers: string[] = route.path
|
|
38
|
+
.split("/")
|
|
39
|
+
.filter(str => str[0] !== ":" && str.length !== 0)
|
|
40
|
+
.map(str => str.split("-").join("_").split(".").join("_"));
|
|
38
41
|
|
|
39
42
|
for (const key of identifiers)
|
|
40
43
|
{
|
|
@@ -14,6 +14,7 @@ export namespace FunctionGenerator
|
|
|
14
14
|
|
|
15
15
|
return [head, body, tail]
|
|
16
16
|
.map(closure => closure(route, query, input))
|
|
17
|
+
.filter(str => !!str)
|
|
17
18
|
.join("\n");
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -43,11 +44,12 @@ export namespace FunctionGenerator
|
|
|
43
44
|
fetchArguments.push("input");
|
|
44
45
|
|
|
45
46
|
// RETURNS WITH FINALIZATION
|
|
46
|
-
return ""
|
|
47
|
+
return "{\n"
|
|
47
48
|
+ " return Fetcher.fetch\n"
|
|
48
49
|
+ " (\n"
|
|
49
50
|
+ fetchArguments.map(param => ` ${param}`).join(",\n") + "\n"
|
|
50
|
-
+ " )
|
|
51
|
+
+ " );\n"
|
|
52
|
+
+ "}";
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
function get_path(route: IRoute, query: IRoute.IParameter | undefined): string
|
|
@@ -142,11 +144,10 @@ export namespace FunctionGenerator
|
|
|
142
144
|
+ `export function ${route.name}\n`
|
|
143
145
|
+ ` (\n`
|
|
144
146
|
+ `${parameters.map(str => ` ${str}`).join(",\n")}\n`
|
|
145
|
-
+ ` ): Promise<${output}
|
|
146
|
-
+ "{";
|
|
147
|
+
+ ` ): Promise<${output}>`;
|
|
147
148
|
}
|
|
148
149
|
|
|
149
|
-
function tail(route: IRoute, query: IRoute.IParameter | undefined, input: IRoute.IParameter | undefined): string
|
|
150
|
+
function tail(route: IRoute, query: IRoute.IParameter | undefined, input: IRoute.IParameter | undefined): string | null
|
|
150
151
|
{
|
|
151
152
|
const types: Pair<string, string>[] = [];
|
|
152
153
|
if (query !== undefined)
|
|
@@ -157,10 +158,9 @@ export namespace FunctionGenerator
|
|
|
157
158
|
types.push(new Pair("Output", route.output));
|
|
158
159
|
|
|
159
160
|
if (types.length === 0)
|
|
160
|
-
return
|
|
161
|
+
return null;
|
|
161
162
|
|
|
162
|
-
return `}\n`
|
|
163
|
-
+ `export namespace ${route.name}\n`
|
|
163
|
+
return `export namespace ${route.name}\n`
|
|
164
164
|
+ "{\n"
|
|
165
165
|
+ (types.map(tuple => ` export type ${tuple.first} = Primitive<${tuple.second}>;`).join("\n")) + "\n"
|
|
166
166
|
+ "}";
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// https://github.com/sindresorhus/strip-json-comments
|
|
2
|
+
|
|
3
|
+
const singleComment: any = Symbol('singleComment');
|
|
4
|
+
const multiComment: any = Symbol('multiComment');
|
|
5
|
+
|
|
6
|
+
const stripWithoutWhitespace = () => '';
|
|
7
|
+
const stripWithWhitespace = (string: string, start: number, end: number) => string.slice(start, end).replace(/\S/g, ' ');
|
|
8
|
+
|
|
9
|
+
const isEscaped = (jsonString: string, quotePosition: number) => {
|
|
10
|
+
let index = quotePosition - 1;
|
|
11
|
+
let backslashCount = 0;
|
|
12
|
+
|
|
13
|
+
while (jsonString[index] === '\\') {
|
|
14
|
+
index -= 1;
|
|
15
|
+
backslashCount += 1;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return Boolean(backslashCount % 2);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function stripJsonComments(jsonString: string, {whitespace = true} = {}) {
|
|
22
|
+
if (typeof jsonString !== 'string') {
|
|
23
|
+
throw new TypeError(`Expected argument \`jsonString\` to be a \`string\`, got \`${typeof jsonString}\``);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const strip = whitespace ? stripWithWhitespace : stripWithoutWhitespace;
|
|
27
|
+
|
|
28
|
+
let isInsideString = false;
|
|
29
|
+
let isInsideComment = false;
|
|
30
|
+
let offset = 0;
|
|
31
|
+
let result = '';
|
|
32
|
+
|
|
33
|
+
for (let index = 0; index < jsonString.length; index++) {
|
|
34
|
+
const currentCharacter = jsonString[index];
|
|
35
|
+
const nextCharacter = jsonString[index + 1];
|
|
36
|
+
|
|
37
|
+
if (!isInsideComment && currentCharacter === '"') {
|
|
38
|
+
const escaped = isEscaped(jsonString, index);
|
|
39
|
+
if (!escaped) {
|
|
40
|
+
isInsideString = !isInsideString;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (isInsideString) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!isInsideComment && currentCharacter + nextCharacter === '//') {
|
|
49
|
+
result += jsonString.slice(offset, index);
|
|
50
|
+
offset = index;
|
|
51
|
+
isInsideComment = singleComment;
|
|
52
|
+
index++;
|
|
53
|
+
} else if (isInsideComment === singleComment && currentCharacter + nextCharacter === '\r\n') {
|
|
54
|
+
index++;
|
|
55
|
+
isInsideComment = false;
|
|
56
|
+
result += strip(jsonString, offset, index);
|
|
57
|
+
offset = index;
|
|
58
|
+
continue;
|
|
59
|
+
} else if (isInsideComment === singleComment && currentCharacter === '\n') {
|
|
60
|
+
isInsideComment = false;
|
|
61
|
+
result += strip(jsonString, offset, index);
|
|
62
|
+
offset = index;
|
|
63
|
+
} else if (!isInsideComment && currentCharacter + nextCharacter === '/*') {
|
|
64
|
+
result += jsonString.slice(offset, index);
|
|
65
|
+
offset = index;
|
|
66
|
+
isInsideComment = multiComment;
|
|
67
|
+
index++;
|
|
68
|
+
continue;
|
|
69
|
+
} else if (isInsideComment === multiComment && currentCharacter + nextCharacter === '*/') {
|
|
70
|
+
index++;
|
|
71
|
+
isInsideComment = false;
|
|
72
|
+
result += strip(jsonString, offset, index + 1);
|
|
73
|
+
offset = index + 1;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return result + (isInsideComment ? (strip as any)(jsonString.slice(offset)) : jsonString.slice(offset));
|
|
79
|
+
}
|
package/tsconfig.json
CHANGED