@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/types/api.d.ts", this.#appRoot);
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
- #generateFinalInterface(types, indent = " ") {
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.#generateFinalInterface(value, indent + " ");
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 types = {};
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 = types;
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.#writeFinalInterface(types);
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.0.6",
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/client": "0.0.11",
35
- "@tuyau/utils": "0.0.3"
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