@tuyau/core 0.0.6 → 0.1.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.
|
@@ -8,9 +8,11 @@ import { BaseCommand, flags } from "@adonisjs/core/ace";
|
|
|
8
8
|
|
|
9
9
|
// src/codegen/api_types_generator.ts
|
|
10
10
|
import { Node } from "ts-morph";
|
|
11
|
+
import matchit from "@poppinss/matchit";
|
|
11
12
|
import { fileURLToPath } from "node:url";
|
|
12
13
|
import { dirname, relative } from "node:path";
|
|
13
14
|
import { existsSync, mkdirSync } from "node:fs";
|
|
15
|
+
import string from "@adonisjs/core/helpers/string";
|
|
14
16
|
import { parseBindingReference } from "@adonisjs/core/helpers";
|
|
15
17
|
var ApiTypesGenerator = class {
|
|
16
18
|
#appRoot;
|
|
@@ -34,7 +36,7 @@ var ApiTypesGenerator = class {
|
|
|
34
36
|
* Create the destination directory if it does not exists
|
|
35
37
|
*/
|
|
36
38
|
#prepareDestination() {
|
|
37
|
-
this.#destination = new URL("./.adonisjs/
|
|
39
|
+
this.#destination = new URL("./.adonisjs/api.ts", this.#appRoot);
|
|
38
40
|
const directory = this.#getDestinationDirectory();
|
|
39
41
|
if (!existsSync(directory)) {
|
|
40
42
|
mkdirSync(directory, { recursive: true });
|
|
@@ -90,13 +92,13 @@ var ApiTypesGenerator = class {
|
|
|
90
92
|
/**
|
|
91
93
|
* Generate the final interface containing all routes, request, and response
|
|
92
94
|
*/
|
|
93
|
-
#
|
|
95
|
+
#generateDefinitionInterface(types, indent = " ") {
|
|
94
96
|
let interfaceContent = "";
|
|
95
97
|
Object.entries(types).forEach(([key, value]) => {
|
|
96
98
|
if (typeof value === "object") {
|
|
97
99
|
interfaceContent += `${indent}'${key}': {
|
|
98
100
|
`;
|
|
99
|
-
interfaceContent += this.#
|
|
101
|
+
interfaceContent += this.#generateDefinitionInterface(value, indent + " ");
|
|
100
102
|
interfaceContent += `${indent}};
|
|
101
103
|
`;
|
|
102
104
|
} else {
|
|
@@ -106,22 +108,6 @@ var ApiTypesGenerator = class {
|
|
|
106
108
|
});
|
|
107
109
|
return interfaceContent;
|
|
108
110
|
}
|
|
109
|
-
/**
|
|
110
|
-
* Write the final interface containing all routes, request, and response
|
|
111
|
-
* in a routes.d.ts file
|
|
112
|
-
*/
|
|
113
|
-
async #writeFinalInterface(types) {
|
|
114
|
-
const file = this.#project.createSourceFile(fileURLToPath(this.#destination), "", {
|
|
115
|
-
overwrite: true
|
|
116
|
-
});
|
|
117
|
-
if (!file)
|
|
118
|
-
throw new Error("Unable to create the api.d.ts file");
|
|
119
|
-
file?.removeText();
|
|
120
|
-
file.insertText(0, (writer) => {
|
|
121
|
-
writer.writeLine(`import type { MakeTuyauRequest, MakeTuyauResponse } from '@tuyau/utils/types'`).writeLine(`import type { InferInput } from '@vinejs/vine/types'`).newLine().writeLine(`export interface AdonisApi {`).write(this.#generateFinalInterface(types, " ")).writeLine(`}`);
|
|
122
|
-
});
|
|
123
|
-
await file.save();
|
|
124
|
-
}
|
|
125
111
|
/**
|
|
126
112
|
* Filter routes to generate based on the ignoreRoutes config
|
|
127
113
|
*/
|
|
@@ -141,8 +127,57 @@ var ApiTypesGenerator = class {
|
|
|
141
127
|
return true;
|
|
142
128
|
});
|
|
143
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Generate a type name based on the route pattern and methods
|
|
132
|
+
*
|
|
133
|
+
* GET /users/:id => UsersIdGet
|
|
134
|
+
*/
|
|
135
|
+
#generateTypeName(route) {
|
|
136
|
+
const remappedSegments = route.pattern.split("/").filter(Boolean).map((segment) => segment.startsWith(":") ? "id" : segment).join(" ");
|
|
137
|
+
const methods = string.pascalCase(route.methods.join(" "));
|
|
138
|
+
return string.pascalCase(remappedSegments) + methods;
|
|
139
|
+
}
|
|
140
|
+
#generateRoutesNameArray(routes, typesByPattern) {
|
|
141
|
+
return routes.map(({ name, pattern, methods }) => {
|
|
142
|
+
const params = matchit.parse(pattern).filter((node) => node.type !== 0).map((node) => node.val);
|
|
143
|
+
let typeName = this.#generateTypeName({ pattern, methods });
|
|
144
|
+
if (!typesByPattern[typeName])
|
|
145
|
+
typeName = "unknown";
|
|
146
|
+
return { params, name, path: pattern, method: methods, types: typeName };
|
|
147
|
+
}).filter((route) => !!route.name);
|
|
148
|
+
}
|
|
149
|
+
async #writeApiFile(options) {
|
|
150
|
+
const path = fileURLToPath(this.#destination);
|
|
151
|
+
const file = this.#project.createSourceFile(path, "", { overwrite: true });
|
|
152
|
+
if (!file)
|
|
153
|
+
throw new Error("Unable to create the api.ts file");
|
|
154
|
+
file.removeText().insertText(0, (writer) => {
|
|
155
|
+
writer.writeLine(`import type { MakeTuyauRequest, MakeTuyauResponse } from '@tuyau/utils/types'`).writeLine(`import type { InferInput } from '@vinejs/vine/types'`).newLine();
|
|
156
|
+
Object.entries(options.typesByPattern).forEach(([key, value]) => {
|
|
157
|
+
writer.writeLine(`type ${key} = {`);
|
|
158
|
+
writer.writeLine(` request: ${value.request}`);
|
|
159
|
+
writer.writeLine(` response: ${value.response}`);
|
|
160
|
+
writer.writeLine(`}`);
|
|
161
|
+
});
|
|
162
|
+
writer.writeLine(`interface AdonisApi {`).write(this.#generateDefinitionInterface(options.definition, " ")).writeLine(`}`);
|
|
163
|
+
writer.writeLine(`const routes = [`);
|
|
164
|
+
for (const route of options.routesNameArray) {
|
|
165
|
+
writer.writeLine(` {`);
|
|
166
|
+
writer.writeLine(` params: ${JSON.stringify(route.params)},`);
|
|
167
|
+
writer.writeLine(` name: '${route.name}',`);
|
|
168
|
+
writer.writeLine(` path: '${route.path}',`);
|
|
169
|
+
writer.writeLine(` method: ${JSON.stringify(route.method)},`);
|
|
170
|
+
writer.writeLine(` types: {} as ${route.types},`);
|
|
171
|
+
writer.writeLine(` },`);
|
|
172
|
+
}
|
|
173
|
+
writer.writeLine(`] as const;`);
|
|
174
|
+
writer.writeLine(`export const api = {`).writeLine(` routes,`).writeLine(` definition: {} as AdonisApi`).writeLine(`}`);
|
|
175
|
+
});
|
|
176
|
+
await file.save();
|
|
177
|
+
}
|
|
144
178
|
async generate() {
|
|
145
|
-
const
|
|
179
|
+
const definition = {};
|
|
180
|
+
const typesByPattern = {};
|
|
146
181
|
const sourcesFiles = this.#project.getSourceFiles();
|
|
147
182
|
const routes = this.#filterRoutesToGenerate(this.#routes);
|
|
148
183
|
for (const route of routes) {
|
|
@@ -165,25 +200,29 @@ var ApiTypesGenerator = class {
|
|
|
165
200
|
const schemaImport = this.#extractRequest(handlerData);
|
|
166
201
|
const methods = route.methods.map((method) => "$" + method.toLowerCase()).filter((method) => method !== "head");
|
|
167
202
|
const segments = route.pattern.split("/").filter(Boolean);
|
|
168
|
-
let currentLevel =
|
|
203
|
+
let currentLevel = definition;
|
|
169
204
|
const relativePath = relative(this.#getDestinationDirectory(), file.getFilePath());
|
|
170
205
|
segments.forEach((segment, i) => {
|
|
171
|
-
if (!currentLevel[segment])
|
|
206
|
+
if (!currentLevel[segment])
|
|
172
207
|
currentLevel[segment] = {};
|
|
173
|
-
}
|
|
174
208
|
currentLevel = currentLevel[segment];
|
|
175
209
|
if (i !== segments.length - 1)
|
|
176
210
|
return;
|
|
211
|
+
const typeName = this.#generateTypeName(route);
|
|
212
|
+
typesByPattern[typeName] = {
|
|
213
|
+
request: schemaImport ? `MakeTuyauRequest<${schemaImport}>` : "unknown",
|
|
214
|
+
response: `MakeTuyauResponse<import('${relativePath}').default['${routeHandler.method}']>`
|
|
215
|
+
};
|
|
177
216
|
currentLevel.$url = {};
|
|
178
|
-
for (const method of methods)
|
|
179
|
-
currentLevel[method] =
|
|
180
|
-
request: schemaImport ? `MakeTuyauRequest<${schemaImport}>` : "unknown",
|
|
181
|
-
response: `MakeTuyauResponse<import('${relativePath}').default['${routeHandler.method}']>`
|
|
182
|
-
};
|
|
183
|
-
}
|
|
217
|
+
for (const method of methods)
|
|
218
|
+
currentLevel[method] = typeName;
|
|
184
219
|
});
|
|
185
220
|
}
|
|
186
|
-
await this.#
|
|
221
|
+
await this.#writeApiFile({
|
|
222
|
+
definition,
|
|
223
|
+
typesByPattern,
|
|
224
|
+
routesNameArray: this.#generateRoutesNameArray(routes, typesByPattern)
|
|
225
|
+
});
|
|
187
226
|
}
|
|
188
227
|
};
|
|
189
228
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tuyau/core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.1.1",
|
|
5
5
|
"description": "",
|
|
6
6
|
"author": "",
|
|
7
7
|
"license": "MIT",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"@poppinss/cliui": "^6.4.1",
|
|
32
32
|
"@poppinss/matchit": "^3.1.2",
|
|
33
33
|
"@types/node": "^20.12.7",
|
|
34
|
-
"@tuyau/
|
|
35
|
-
"@tuyau/
|
|
34
|
+
"@tuyau/utils": "0.0.4",
|
|
35
|
+
"@tuyau/client": "0.1.0"
|
|
36
36
|
},
|
|
37
37
|
"publishConfig": {
|
|
38
38
|
"access": "public",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
},
|
|
63
63
|
"scripts": {
|
|
64
64
|
"clean": "del-cli build",
|
|
65
|
-
"copy:templates": "copyfiles \"stubs/**/*.stub\" build",
|
|
65
|
+
"copy:templates": "copyfiles --up 1 \"stubs/**/*.stub\" build",
|
|
66
66
|
"typecheck": "tsc --noEmit",
|
|
67
67
|
"format": "prettier --write .",
|
|
68
68
|
"quick:test": "node --import=./tsnode.esm.js --enable-source-maps bin/test.ts",
|
|
File without changes
|