@tuyau/core 0.1.0 → 0.1.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.
@@ -3,6 +3,7 @@ import {
3
3
  } from "../chunk-3KNDPG6Y.js";
4
4
 
5
5
  // commands/generate.ts
6
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
6
7
  import { Project, QuoteKind } from "ts-morph";
7
8
  import { BaseCommand, flags } from "@adonisjs/core/ace";
8
9
 
@@ -30,13 +31,13 @@ var ApiTypesGenerator = class {
30
31
  this.#prepareDestination();
31
32
  }
32
33
  #getDestinationDirectory() {
33
- return dirname(this.#destination.pathname);
34
+ return dirname(this.#destination);
34
35
  }
35
36
  /**
36
37
  * Create the destination directory if it does not exists
37
38
  */
38
39
  #prepareDestination() {
39
- this.#destination = new URL("./.adonisjs/api.ts", this.#appRoot);
40
+ this.#destination = fileURLToPath(new URL("./.adonisjs/api.ts", this.#appRoot));
40
41
  const directory = this.#getDestinationDirectory();
41
42
  if (!existsSync(directory)) {
42
43
  mkdirSync(directory, { recursive: true });
@@ -109,34 +110,59 @@ var ApiTypesGenerator = class {
109
110
  return interfaceContent;
110
111
  }
111
112
  /**
112
- * Filter routes to generate based on the ignoreRoutes config
113
+ * Filter routes to generate based on the config
113
114
  */
114
- #filterRoutesToGenerate(routes) {
115
+ #filterRoutes(routes, mode) {
116
+ const config = this.#config.codegen?.[mode];
117
+ if (!config || !config.only && !config.except)
118
+ return routes;
115
119
  return routes.filter((route) => {
116
- if (!this.#config.codegen?.ignoreRoutes)
117
- return true;
118
- if (typeof this.#config.codegen?.ignoreRoutes === "function") {
119
- return !this.#config.codegen.ignoreRoutes(route);
120
+ if (typeof config.only === "function")
121
+ return config.only(route);
122
+ if (typeof config.except === "function")
123
+ return !config.except(route);
124
+ if (config.only) {
125
+ for (const pattern of config.only) {
126
+ if (pattern instanceof RegExp && pattern.test(route.pattern))
127
+ return true;
128
+ if (route.pattern === pattern)
129
+ return true;
130
+ }
131
+ return false;
120
132
  }
121
- for (const ignore of this.#config.codegen.ignoreRoutes) {
122
- if (typeof ignore === "string" && route.pattern === ignore)
123
- return false;
124
- if (ignore instanceof RegExp && ignore.test(route.pattern))
125
- return false;
133
+ if (config.except) {
134
+ for (const pattern of config.except) {
135
+ if (pattern instanceof RegExp && pattern.test(route.pattern))
136
+ return false;
137
+ if (route.pattern === pattern)
138
+ return false;
139
+ }
140
+ return true;
126
141
  }
127
142
  return true;
128
143
  });
129
144
  }
130
- #generateRoutesNameArray(routes) {
145
+ /**
146
+ * Generate a type name based on the route pattern and methods
147
+ *
148
+ * GET /users/:id => UsersIdGet
149
+ */
150
+ #generateTypeName(route) {
151
+ const remappedSegments = route.pattern.split("/").filter(Boolean).map((segment) => segment.startsWith(":") ? "id" : segment).join(" ");
152
+ const methods = string.pascalCase(route.methods.join(" "));
153
+ return string.pascalCase(remappedSegments) + methods;
154
+ }
155
+ #generateRoutesNameArray(routes, typesByPattern) {
131
156
  return routes.map(({ name, pattern, methods }) => {
132
157
  const params = matchit.parse(pattern).filter((node) => node.type !== 0).map((node) => node.val);
133
- const typeName = string.pascalCase(string.slug(pattern)) + string.pascalCase(methods.join(" "));
158
+ let typeName = this.#generateTypeName({ pattern, methods });
159
+ if (!typesByPattern[typeName])
160
+ typeName = "unknown";
134
161
  return { params, name, path: pattern, method: methods, types: typeName };
135
162
  }).filter((route) => !!route.name);
136
163
  }
137
164
  async #writeApiFile(options) {
138
- const path = fileURLToPath(this.#destination);
139
- const file = this.#project.createSourceFile(path, "", { overwrite: true });
165
+ const file = this.#project.createSourceFile(this.#destination, "", { overwrite: true });
140
166
  if (!file)
141
167
  throw new Error("Unable to create the api.ts file");
142
168
  file.removeText().insertText(0, (writer) => {
@@ -147,7 +173,7 @@ var ApiTypesGenerator = class {
147
173
  writer.writeLine(` response: ${value.response}`);
148
174
  writer.writeLine(`}`);
149
175
  });
150
- writer.writeLine(`interface AdonisApi {`).write(this.#generateDefinitionInterface(options.definition, " ")).writeLine(`}`);
176
+ writer.writeLine(`export interface ApiDefinition {`).write(this.#generateDefinitionInterface(options.definition, " ")).writeLine(`}`);
151
177
  writer.writeLine(`const routes = [`);
152
178
  for (const route of options.routesNameArray) {
153
179
  writer.writeLine(` {`);
@@ -159,7 +185,11 @@ var ApiTypesGenerator = class {
159
185
  writer.writeLine(` },`);
160
186
  }
161
187
  writer.writeLine(`] as const;`);
162
- writer.writeLine(`export const api = {`).writeLine(` routes,`).writeLine(` definition: {} as AdonisApi`).writeLine(`}`);
188
+ writer.writeLine(`export const api = {`).writeLine(` routes,`).writeLine(` definition: {} as ApiDefinition`).writeLine(`}`);
189
+ writer.writeLine(`declare module '@tuyau/inertia/types' {`);
190
+ writer.writeLine(` type ApiDefinition = typeof api`);
191
+ writer.writeLine(` export interface Api extends ApiDefinition {}`);
192
+ writer.writeLine(`}`);
163
193
  });
164
194
  await file.save();
165
195
  }
@@ -167,7 +197,7 @@ var ApiTypesGenerator = class {
167
197
  const definition = {};
168
198
  const typesByPattern = {};
169
199
  const sourcesFiles = this.#project.getSourceFiles();
170
- const routes = this.#filterRoutesToGenerate(this.#routes);
200
+ const routes = this.#filterRoutes(this.#routes, "definitions");
171
201
  for (const route of routes) {
172
202
  if (typeof route.handler === "function")
173
203
  continue;
@@ -196,22 +226,21 @@ var ApiTypesGenerator = class {
196
226
  currentLevel = currentLevel[segment];
197
227
  if (i !== segments.length - 1)
198
228
  return;
199
- const typeName = string.pascalCase(string.slug(route.pattern)) + string.pascalCase(methods.join(""));
229
+ const typeName = this.#generateTypeName(route);
200
230
  typesByPattern[typeName] = {
201
231
  request: schemaImport ? `MakeTuyauRequest<${schemaImport}>` : "unknown",
202
232
  response: `MakeTuyauResponse<import('${relativePath}').default['${routeHandler.method}']>`
203
233
  };
204
234
  currentLevel.$url = {};
205
- for (const method of methods) {
206
- currentLevel[method] = `${string.pascalCase(typeName)}`;
207
- }
235
+ for (const method of methods)
236
+ currentLevel[method] = typeName;
208
237
  });
209
238
  }
210
- await this.#writeApiFile({
211
- definition,
212
- typesByPattern,
213
- routesNameArray: this.#generateRoutesNameArray(routes)
214
- });
239
+ const routesNameArray = this.#generateRoutesNameArray(
240
+ this.#filterRoutes(routes, "routes"),
241
+ typesByPattern
242
+ );
243
+ await this.#writeApiFile({ definition, typesByPattern, routesNameArray });
215
244
  }
216
245
  };
217
246
 
@@ -234,7 +263,7 @@ var CodegenTypes = class extends BaseCommand {
234
263
  async run() {
235
264
  const project = new Project({
236
265
  manipulationSettings: { quoteKind: QuoteKind.Single },
237
- tsConfigFilePath: new URL("./tsconfig.json", this.app.appRoot).pathname
266
+ tsConfigFilePath: fileURLToPath2(new URL("./tsconfig.json", this.app.appRoot))
238
267
  });
239
268
  const apiTypesGenerator = new ApiTypesGenerator({
240
269
  project,
@@ -6,9 +6,14 @@ import { defineConfig } from '@tuyau/core'
6
6
  const tuyauConfig = defineConfig({
7
7
  codegen: {
8
8
  /**
9
- * List of routes to ignore during code generation
9
+ * Filters the definitions and named routes to be generated
10
10
  */
11
- ignoreRoutes: []
11
+ // definitions: {
12
+ // only: [],
13
+ // }
14
+ // routes: {
15
+ // only: [],
16
+ // }
12
17
  }
13
18
  })
14
19
 
@@ -1,12 +1,25 @@
1
1
  import { RouteJSON } from '@adonisjs/core/types/http';
2
2
 
3
+ type Without<T, U> = {
4
+ [P in Exclude<keyof T, keyof U>]?: never;
5
+ };
6
+ type XOR<T, U> = T | U extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;
7
+ type FilterConfig<FilterType> = XOR<{
8
+ only: FilterType;
9
+ }, {
10
+ except: FilterType;
11
+ }>;
3
12
  interface TuyauConfig {
4
13
  codegen?: {
5
14
  /**
6
- * List of routes to ignore during code generation
15
+ * Filters the definitions to be generated
7
16
  */
8
- ignoreRoutes?: Array<string | RegExp> | ((route: RouteJSON) => boolean);
17
+ definitions?: FilterConfig<Array<string | RegExp> | ((route: RouteJSON) => boolean)>;
18
+ /**
19
+ * Filters the named routes to be generated
20
+ */
21
+ routes?: FilterConfig<Array<string | RegExp> | ((route: RouteJSON) => boolean)>;
9
22
  };
10
23
  }
11
24
 
12
- export type { TuyauConfig };
25
+ export type { FilterConfig, TuyauConfig };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tuyau/core",
3
3
  "type": "module",
4
- "version": "0.1.0",
4
+ "version": "0.1.2",
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/utils": "0.0.4",
35
- "@tuyau/client": "0.1.0"
34
+ "@tuyau/client": "0.1.1",
35
+ "@tuyau/utils": "0.0.4"
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",