vitek-plugin 0.1.2-beta.5 → 0.2.0-beta

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.
Files changed (177) hide show
  1. package/README.md +35 -4
  2. package/dist/adapters/vite/dev-server-middleware.d.ts +8 -0
  3. package/dist/adapters/vite/dev-server-middleware.d.ts.map +1 -0
  4. package/dist/adapters/vite/dev-server-middleware.js +30 -0
  5. package/dist/adapters/vite/dev-server-state.d.ts +41 -0
  6. package/dist/adapters/vite/dev-server-state.d.ts.map +1 -0
  7. package/dist/adapters/vite/dev-server-state.js +191 -0
  8. package/dist/adapters/vite/dev-server.d.ts +2 -21
  9. package/dist/adapters/vite/dev-server.d.ts.map +1 -1
  10. package/dist/adapters/vite/dev-server.js +7 -379
  11. package/dist/adapters/vite/path-utils.d.ts +20 -0
  12. package/dist/adapters/vite/path-utils.d.ts.map +1 -0
  13. package/dist/adapters/vite/path-utils.js +46 -0
  14. package/dist/adapters/vite/path-utils.test.d.ts +2 -0
  15. package/dist/adapters/vite/path-utils.test.d.ts.map +1 -0
  16. package/dist/adapters/vite/path-utils.test.js +79 -0
  17. package/dist/build/build-api-bundle.d.ts +1 -0
  18. package/dist/build/build-api-bundle.d.ts.map +1 -1
  19. package/dist/build/build-api-bundle.js +38 -3
  20. package/dist/build/build-api-bundle.test.d.ts +2 -0
  21. package/dist/build/build-api-bundle.test.d.ts.map +1 -0
  22. package/dist/build/build-api-bundle.test.js +50 -0
  23. package/dist/build/build-sockets-bundle.test.d.ts +2 -0
  24. package/dist/build/build-sockets-bundle.test.d.ts.map +1 -0
  25. package/dist/build/build-sockets-bundle.test.js +49 -0
  26. package/dist/cli/cli.d.ts +8 -0
  27. package/dist/cli/cli.d.ts.map +1 -0
  28. package/dist/cli/cli.js +25 -0
  29. package/dist/cli/fixtures/serve-config/vitek.config.d.mts +6 -0
  30. package/dist/cli/fixtures/serve-config/vitek.config.d.mts.map +1 -0
  31. package/dist/cli/fixtures/serve-config/vitek.config.mjs +19 -0
  32. package/dist/cli/init.d.ts +15 -0
  33. package/dist/cli/init.d.ts.map +1 -0
  34. package/dist/cli/init.js +99 -0
  35. package/dist/cli/init.test.d.ts +2 -0
  36. package/dist/cli/init.test.d.ts.map +1 -0
  37. package/dist/cli/init.test.js +117 -0
  38. package/dist/cli/mcp-project-config.d.ts +8 -0
  39. package/dist/cli/mcp-project-config.d.ts.map +1 -0
  40. package/dist/cli/mcp-project-config.js +26 -0
  41. package/dist/cli/mcp-project.d.ts +2 -0
  42. package/dist/cli/mcp-project.d.ts.map +1 -0
  43. package/dist/cli/mcp-project.js +101 -0
  44. package/dist/cli/serve.d.ts +27 -1
  45. package/dist/cli/serve.d.ts.map +1 -1
  46. package/dist/cli/serve.js +85 -10
  47. package/dist/cli/serve.test.d.ts +2 -0
  48. package/dist/cli/serve.test.d.ts.map +1 -0
  49. package/dist/cli/serve.test.js +108 -0
  50. package/dist/core/asyncapi/generate.d.ts +5 -3
  51. package/dist/core/asyncapi/generate.d.ts.map +1 -1
  52. package/dist/core/asyncapi/generate.test.d.ts +2 -0
  53. package/dist/core/asyncapi/generate.test.d.ts.map +1 -0
  54. package/dist/core/asyncapi/generate.test.js +120 -0
  55. package/dist/core/context/create-context.d.ts +2 -0
  56. package/dist/core/context/create-context.d.ts.map +1 -1
  57. package/dist/core/file-system/extract-type-from-file.d.ts +4 -0
  58. package/dist/core/file-system/extract-type-from-file.d.ts.map +1 -0
  59. package/dist/core/file-system/extract-type-from-file.js +77 -0
  60. package/dist/core/file-system/extract-type-from-file.test.d.ts +2 -0
  61. package/dist/core/file-system/extract-type-from-file.test.d.ts.map +1 -0
  62. package/dist/core/file-system/extract-type-from-file.test.js +75 -0
  63. package/dist/core/file-system/watch-api-dir.d.ts +4 -1
  64. package/dist/core/file-system/watch-api-dir.d.ts.map +1 -1
  65. package/dist/core/file-system/watch-api-dir.js +31 -6
  66. package/dist/core/file-system/watch-api-dir.test.d.ts +2 -0
  67. package/dist/core/file-system/watch-api-dir.test.d.ts.map +1 -0
  68. package/dist/core/file-system/watch-api-dir.test.js +38 -0
  69. package/dist/core/generation/run-file-generation.d.ts +24 -0
  70. package/dist/core/generation/run-file-generation.d.ts.map +1 -0
  71. package/dist/core/generation/run-file-generation.js +90 -0
  72. package/dist/core/generation/run-file-generation.test.d.ts +2 -0
  73. package/dist/core/generation/run-file-generation.test.d.ts.map +1 -0
  74. package/dist/core/generation/run-file-generation.test.js +151 -0
  75. package/dist/core/introspection/manifest.d.ts +24 -0
  76. package/dist/core/introspection/manifest.d.ts.map +1 -0
  77. package/dist/core/introspection/manifest.js +41 -0
  78. package/dist/core/introspection/manifest.test.d.ts +2 -0
  79. package/dist/core/introspection/manifest.test.d.ts.map +1 -0
  80. package/dist/core/introspection/manifest.test.js +62 -0
  81. package/dist/core/middleware/get-applicable-middlewares.d.ts +7 -0
  82. package/dist/core/middleware/get-applicable-middlewares.d.ts.map +1 -1
  83. package/dist/core/middleware/get-applicable-middlewares.js +23 -15
  84. package/dist/core/middleware/get-applicable-middlewares.test.js +36 -1
  85. package/dist/core/openapi/generate.d.ts +5 -74
  86. package/dist/core/openapi/generate.d.ts.map +1 -1
  87. package/dist/core/openapi/generate.js +4 -419
  88. package/dist/core/openapi/generate.test.d.ts +2 -0
  89. package/dist/core/openapi/generate.test.d.ts.map +1 -0
  90. package/dist/core/openapi/generate.test.js +184 -0
  91. package/dist/core/openapi/jsdoc.d.ts +3 -0
  92. package/dist/core/openapi/jsdoc.d.ts.map +1 -0
  93. package/dist/core/openapi/jsdoc.js +68 -0
  94. package/dist/core/openapi/jsdoc.test.d.ts +2 -0
  95. package/dist/core/openapi/jsdoc.test.d.ts.map +1 -0
  96. package/dist/core/openapi/jsdoc.test.js +111 -0
  97. package/dist/core/openapi/spec-builder.d.ts +4 -0
  98. package/dist/core/openapi/spec-builder.d.ts.map +1 -0
  99. package/dist/core/openapi/spec-builder.js +257 -0
  100. package/dist/core/openapi/spec-builder.test.d.ts +2 -0
  101. package/dist/core/openapi/spec-builder.test.d.ts.map +1 -0
  102. package/dist/core/openapi/spec-builder.test.js +93 -0
  103. package/dist/core/openapi/types.d.ts +42 -0
  104. package/dist/core/openapi/types.d.ts.map +1 -0
  105. package/dist/core/openapi/types.js +5 -0
  106. package/dist/core/server/cors.d.ts +29 -0
  107. package/dist/core/server/cors.d.ts.map +1 -0
  108. package/dist/core/server/cors.js +55 -0
  109. package/dist/core/server/cors.test.d.ts +2 -0
  110. package/dist/core/server/cors.test.d.ts.map +1 -0
  111. package/dist/core/server/cors.test.js +49 -0
  112. package/dist/core/server/proxy.d.ts +16 -0
  113. package/dist/core/server/proxy.d.ts.map +1 -0
  114. package/dist/core/server/proxy.js +20 -0
  115. package/dist/core/server/proxy.test.d.ts +2 -0
  116. package/dist/core/server/proxy.test.d.ts.map +1 -0
  117. package/dist/core/server/proxy.test.js +53 -0
  118. package/dist/core/server/request-handler.d.ts +17 -3
  119. package/dist/core/server/request-handler.d.ts.map +1 -1
  120. package/dist/core/server/request-handler.js +192 -84
  121. package/dist/core/server/request-handler.test.js +287 -22
  122. package/dist/core/socket/socket-handler.test.d.ts +2 -0
  123. package/dist/core/socket/socket-handler.test.d.ts.map +1 -0
  124. package/dist/core/socket/socket-handler.test.js +107 -0
  125. package/dist/core/types/schema.test.d.ts +2 -0
  126. package/dist/core/types/schema.test.d.ts.map +1 -0
  127. package/dist/core/types/schema.test.js +41 -0
  128. package/dist/core/validation/types.d.ts +2 -1
  129. package/dist/core/validation/types.d.ts.map +1 -1
  130. package/dist/core/validation/validator.d.ts +4 -16
  131. package/dist/core/validation/validator.d.ts.map +1 -1
  132. package/dist/core/validation/validator.js +4 -16
  133. package/dist/index.d.ts +6 -1
  134. package/dist/index.d.ts.map +1 -1
  135. package/dist/index.js +2 -1
  136. package/dist/plugin/context.d.ts +15 -0
  137. package/dist/plugin/context.d.ts.map +1 -0
  138. package/dist/plugin/context.js +12 -0
  139. package/dist/plugin/options.d.ts +46 -0
  140. package/dist/plugin/options.d.ts.map +1 -0
  141. package/dist/plugin/options.js +1 -0
  142. package/dist/plugin/plugin-api.d.ts +49 -0
  143. package/dist/plugin/plugin-api.d.ts.map +1 -0
  144. package/dist/plugin/plugin-api.js +5 -0
  145. package/dist/plugin/vitek-build.d.ts +7 -0
  146. package/dist/plugin/vitek-build.d.ts.map +1 -0
  147. package/dist/plugin/vitek-build.js +104 -0
  148. package/dist/plugin/vitek-config.d.ts +4 -0
  149. package/dist/plugin/vitek-config.d.ts.map +1 -0
  150. package/dist/plugin/vitek-config.js +51 -0
  151. package/dist/plugin/vitek-config.test.d.ts +2 -0
  152. package/dist/plugin/vitek-config.test.d.ts.map +1 -0
  153. package/dist/plugin/vitek-config.test.js +62 -0
  154. package/dist/plugin/vitek-dev.d.ts +7 -0
  155. package/dist/plugin/vitek-dev.d.ts.map +1 -0
  156. package/dist/plugin/vitek-dev.js +71 -0
  157. package/dist/plugin/vitek-preview.d.ts +7 -0
  158. package/dist/plugin/vitek-preview.d.ts.map +1 -0
  159. package/dist/plugin/vitek-preview.js +107 -0
  160. package/dist/plugin/vitek-resolve.d.ts +7 -0
  161. package/dist/plugin/vitek-resolve.d.ts.map +1 -0
  162. package/dist/plugin/vitek-resolve.js +25 -0
  163. package/dist/plugin/vitek-transform.d.ts +7 -0
  164. package/dist/plugin/vitek-transform.d.ts.map +1 -0
  165. package/dist/plugin/vitek-transform.js +55 -0
  166. package/dist/plugin/vitek.d.ts +10 -0
  167. package/dist/plugin/vitek.d.ts.map +1 -0
  168. package/dist/plugin/vitek.js +27 -0
  169. package/dist/plugin.d.ts +3 -32
  170. package/dist/plugin.d.ts.map +1 -1
  171. package/dist/plugin.js +2 -246
  172. package/dist/plugin.test.js +114 -28
  173. package/dist/shared/response-helpers.d.ts +21 -0
  174. package/dist/shared/response-helpers.d.ts.map +1 -1
  175. package/dist/shared/response-helpers.js +41 -0
  176. package/dist/shared/response-helpers.test.js +54 -1
  177. package/package.json +19 -4
@@ -1,371 +1,15 @@
1
- /**
2
- * Adapter for integration with Vite development server
3
- * Thin layer that connects core → Vite
4
- */
5
- import * as path from 'path';
6
- import * as fs from 'fs';
7
- import { scanApiDirectory } from '../../core/file-system/scan-api-dir.js';
8
- /**
9
- * Detects if the project uses TypeScript by checking if tsconfig.json exists
10
- */
11
- function isTypeScriptProject(root) {
12
- const tsconfigPath = path.join(root, 'tsconfig.json');
13
- return fs.existsSync(tsconfigPath);
14
- }
15
- import { watchApiDirectory } from '../../core/file-system/watch-api-dir.js';
16
- import { createRoute } from '../../core/routing/route-parser.js';
17
- import { createRequestHandler } from '../../core/server/request-handler.js';
18
- import { routesToSchema } from '../../core/types/schema.js';
19
- import { generateTypesFile, generateServicesFile } from '../../core/types/generate.js';
20
- import { generateSocketTypesFile } from '../../core/types/generate-socket-types.js';
21
- import { generateSocketServicesFile } from '../../core/types/generate-socket-services.js';
22
- import { patternToRegex } from '../../core/normalize/normalize-path.js';
23
- import { createSocketHandler } from '../../core/socket/socket-handler.js';
24
- import { generateOpenApiFile, generateApiDocsHtml } from '../../core/openapi/generate.js';
25
- import { generateAsyncApiFile } from '../../core/asyncapi/generate.js';
26
- import { API_BASE_PATH, SOCKET_BASE_PATH, GENERATED_TYPES_FILE, GENERATED_SERVICES_FILE, GENERATED_SOCKET_TYPES_FILE, GENERATED_SOCKET_SERVICES_FILE, } from '../../shared/constants.js';
27
- function deduplicateRoutesByKey(routes) {
28
- const seen = new Set();
29
- return routes.filter((r) => {
30
- const key = `${r.method}:${r.pattern}`;
31
- if (seen.has(key))
32
- return false;
33
- seen.add(key);
34
- return true;
35
- });
36
- }
37
- function deduplicateSocketsByPattern(sockets) {
38
- const seen = new Set();
39
- return sockets.filter((s) => {
40
- if (seen.has(s.pattern))
41
- return false;
42
- seen.add(s.pattern);
43
- return true;
44
- });
45
- }
46
- /**
47
- * Development server state
48
- */
49
- class DevServerState {
50
- options;
51
- routes = [];
52
- middlewares = [];
53
- sockets = [];
54
- watcher = null;
55
- constructor(options) {
56
- this.options = options;
57
- }
58
- /**
59
- * Initializes the server: scan, load routes and middleware
60
- */
61
- async initialize() {
62
- await this.reload(false); // Don't show "Reloading" on initialization
63
- this.setupWatcher();
64
- }
65
- /**
66
- * Reloads routes and middleware
67
- */
68
- async reload(showReloadLog = true) {
69
- if (showReloadLog) {
70
- this.options.logger.info('Reloading API routes...');
71
- }
72
- const scanResult = scanApiDirectory(this.options.apiDir);
73
- this.middlewares.length = 0;
74
- for (const middlewareInfo of scanResult.middlewares) {
75
- try {
76
- const relativePath = path.relative(this.options.root, middlewareInfo.path);
77
- const vitePath = `/${relativePath.replace(/\\/g, '/')}`;
78
- const middlewareModule = await this.options.viteServer.ssrLoadModule(vitePath);
79
- const middleware = middlewareModule.default || middlewareModule.middleware;
80
- let middlewareArray = [];
81
- if (Array.isArray(middleware)) {
82
- middlewareArray = middleware;
83
- }
84
- else if (typeof middleware === 'function') {
85
- middlewareArray = [middleware];
86
- }
87
- if (middlewareArray.length > 0) {
88
- this.middlewares.push({
89
- middleware: middlewareArray,
90
- basePattern: middlewareInfo.basePattern,
91
- });
92
- }
93
- }
94
- catch (error) {
95
- this.options.logger.warn(`Failed to load middleware ${middlewareInfo.path}: ${error instanceof Error ? error.message : String(error)}`);
96
- }
97
- }
98
- const totalMiddlewareCount = this.middlewares.reduce((sum, m) => sum + m.middleware.length, 0);
99
- this.options.logger.middlewareLoaded(totalMiddlewareCount);
100
- this.routes.length = 0;
101
- for (const parsedRoute of scanResult.routes) {
102
- try {
103
- const relativePath = path.relative(this.options.root, parsedRoute.file);
104
- const vitePath = `/${relativePath.replace(/\\/g, '/')}`;
105
- const handlerModule = await this.options.viteServer.ssrLoadModule(vitePath);
106
- const handler = handlerModule.default || handlerModule.handler || handlerModule[parsedRoute.method];
107
- if (typeof handler !== 'function') {
108
- this.options.logger.warn(`Route file ${parsedRoute.file} does not export a handler function`);
109
- continue;
110
- }
111
- const bodyType = extractBodyTypeFromFile(parsedRoute.file);
112
- const queryType = extractQueryTypeFromFile(parsedRoute.file);
113
- const route = createRoute(parsedRoute, handler, bodyType, queryType);
114
- this.routes.push(route);
115
- }
116
- catch (error) {
117
- this.options.logger.error(`Failed to load route ${parsedRoute.file}: ${error instanceof Error ? error.message : String(error)}`);
118
- }
119
- }
120
- this.routes = deduplicateRoutesByKey(this.routes);
121
- const routesInfo = this.routes.map(r => ({
122
- method: r.method,
123
- pattern: r.pattern,
124
- }));
125
- this.options.logger.routesRegistered(routesInfo, API_BASE_PATH);
126
- this.sockets.length = 0;
127
- const socketsEnabled = this.options.sockets !== false;
128
- if (socketsEnabled) {
129
- for (const parsedSocket of scanResult.sockets) {
130
- try {
131
- const relativePath = path.relative(this.options.root, parsedSocket.file);
132
- const vitePath = `/${relativePath.replace(/\\/g, '/')}`;
133
- const handlerModule = await this.options.viteServer.ssrLoadModule(vitePath);
134
- const handler = handlerModule.default ?? handlerModule.handler;
135
- if (typeof handler !== 'function') {
136
- this.options.logger.warn(`Socket file ${parsedSocket.file} does not export a handler function`);
137
- continue;
138
- }
139
- const regex = patternToRegex(parsedSocket.pattern);
140
- this.sockets.push({
141
- pattern: parsedSocket.pattern,
142
- params: parsedSocket.params,
143
- file: parsedSocket.file,
144
- regex,
145
- handler,
146
- });
147
- }
148
- catch (error) {
149
- this.options.logger.error(`Failed to load socket ${parsedSocket.file}: ${error instanceof Error ? error.message : String(error)}`);
150
- }
151
- }
152
- }
153
- this.sockets = deduplicateSocketsByPattern(this.sockets);
154
- const socketBasePath = this.options.socketBasePath ?? SOCKET_BASE_PATH;
155
- this.options.logger.socketsRegistered(this.sockets.map((s) => ({ pattern: s.pattern })), socketBasePath);
156
- await this.generateTypes();
157
- }
158
- /**
159
- * Sets up watcher to reload when files change
160
- */
161
- setupWatcher() {
162
- if (this.watcher) {
163
- this.watcher.close();
164
- }
165
- this.watcher = watchApiDirectory(this.options.apiDir, async (event, filePath) => {
166
- this.options.logger.info(`API file ${event}: ${filePath}`);
167
- await this.reload();
168
- });
169
- }
170
- /**
171
- * Generates types and services files
172
- */
173
- async generateTypes() {
174
- try {
175
- const schema = routesToSchema(this.routes);
176
- const isTypeScript = isTypeScriptProject(this.options.root);
177
- if (isTypeScript) {
178
- const typesPath = path.join(this.options.root, 'src', GENERATED_TYPES_FILE);
179
- await generateTypesFile(typesPath, schema, API_BASE_PATH);
180
- const relativeTypesPath = path.relative(this.options.root, typesPath);
181
- this.options.logger.typesGenerated(`./${relativeTypesPath.replace(/\\/g, '/')}`);
182
- }
183
- const servicesFileName = isTypeScript ? GENERATED_SERVICES_FILE : 'api.services.js';
184
- const servicesPath = path.join(this.options.root, 'src', servicesFileName);
185
- await generateServicesFile(servicesPath, schema, API_BASE_PATH, isTypeScript);
186
- const relativeServicesPath = path.relative(this.options.root, servicesPath);
187
- this.options.logger.servicesGenerated(`./${relativeServicesPath.replace(/\\/g, '/')}`);
188
- if (this.sockets.length > 0) {
189
- const socketSchema = this.sockets.map((s) => ({
190
- pattern: s.pattern,
191
- params: s.params,
192
- file: s.file,
193
- }));
194
- const socketBasePath = this.options.socketBasePath ?? SOCKET_BASE_PATH;
195
- if (isTypeScript) {
196
- const socketTypesPath = path.join(this.options.root, 'src', GENERATED_SOCKET_TYPES_FILE);
197
- await generateSocketTypesFile(socketTypesPath, socketSchema, socketBasePath);
198
- const relativeSocketTypesPath = path.relative(this.options.root, socketTypesPath);
199
- this.options.logger.typesGenerated(`./${relativeSocketTypesPath.replace(/\\/g, '/')}`);
200
- }
201
- const socketServicesFileName = isTypeScript ? GENERATED_SOCKET_SERVICES_FILE : 'socket.services.js';
202
- const socketServicesPath = path.join(this.options.root, 'src', socketServicesFileName);
203
- await generateSocketServicesFile(socketServicesPath, socketSchema, socketBasePath, isTypeScript);
204
- const relativeSocketServicesPath = path.relative(this.options.root, socketServicesPath);
205
- this.options.logger.servicesGenerated(`./${relativeSocketServicesPath.replace(/\\/g, '/')}`);
206
- }
207
- // Generate OpenAPI spec if enabled
208
- await this.generateOpenApi();
209
- }
210
- catch (error) {
211
- this.options.logger.error(`Failed to generate types: ${error instanceof Error ? error.message : String(error)}`);
212
- }
213
- }
214
- /**
215
- * Generates OpenAPI specification and Swagger UI
216
- */
217
- async generateOpenApi() {
218
- const { openApi } = this.options;
219
- if (!openApi) {
220
- return;
221
- }
222
- try {
223
- const openApiOptions = typeof openApi === 'boolean'
224
- ? {
225
- apiBasePath: API_BASE_PATH,
226
- }
227
- : { ...openApi, apiBasePath: API_BASE_PATH };
228
- // Generate openapi.json
229
- const openApiPath = path.join(this.options.root, 'public', 'openapi.json');
230
- await generateOpenApiFile(openApiPath, this.routes, openApiOptions);
231
- const relativeOpenApiPath = path.relative(this.options.root, openApiPath);
232
- this.options.logger.info(`OpenAPI spec generated: ./${relativeOpenApiPath.replace(/\\/g, '/')}`);
233
- const port = this.options.viteServer.config.server?.port || 5173;
234
- const socketBasePath = this.options.socketBasePath ?? SOCKET_BASE_PATH;
235
- const hasSockets = this.sockets.length > 0;
236
- if (hasSockets) {
237
- const asyncApiPath = path.join(this.options.root, 'public', 'asyncapi.json');
238
- await generateAsyncApiFile(asyncApiPath, this.sockets, socketBasePath, {
239
- serverUrl: `ws://localhost:${port}`,
240
- });
241
- const relativeAsyncPath = path.relative(this.options.root, asyncApiPath);
242
- this.options.logger.info(`AsyncAPI spec generated: ./${relativeAsyncPath.replace(/\\/g, '/')}`);
243
- }
244
- // Generate API docs HTML (Swagger UI only, or REST + WebSockets tabs)
245
- const apiDocsPath = path.join(this.options.root, 'public', 'api-docs.html');
246
- const title = openApiOptions.info?.title || 'Vitek API';
247
- const apiDocsHtml = generateApiDocsHtml('/openapi.json', title, {
248
- asyncApiJsonPath: hasSockets ? '/asyncapi.json' : undefined,
249
- });
250
- fs.writeFileSync(apiDocsPath, apiDocsHtml, 'utf-8');
251
- const relativeApiDocsPath = path.relative(this.options.root, apiDocsPath);
252
- this.options.logger.info(`API docs at: ./${relativeApiDocsPath.replace(/\\/g, '/')} → http://localhost:${port}/api-docs.html`);
253
- }
254
- catch (error) {
255
- this.options.logger.warn(`Failed to generate OpenAPI spec: ${error instanceof Error ? error.message : String(error)}`);
256
- }
257
- }
258
- /**
259
- * Cleans up resources
260
- */
261
- cleanup() {
262
- if (this.watcher) {
263
- this.watcher.close();
264
- this.watcher = null;
265
- }
266
- }
267
- }
268
- /**
269
- * Extracts the body type from a route file
270
- * Looks for export type Body = ... or export interface Body { ... }
271
- * Returns the complete type definition as a string
272
- */
273
- function extractBodyTypeFromFile(filePath) {
274
- return extractTypeFromFile(filePath, 'Body');
275
- }
276
- /**
277
- * Extracts the query type from a route file
278
- * Looks for export type Query = ... or export interface Query { ... }
279
- * Returns the complete type definition as a string
280
- */
281
- function extractQueryTypeFromFile(filePath) {
282
- return extractTypeFromFile(filePath, 'Query');
283
- }
284
- /**
285
- * Extracts a type (Body or Query) from a route file via regex. AST-based extraction could be used in a future version.
286
- */
287
- function extractTypeFromFile(filePath, typeName) {
288
- try {
289
- const content = fs.readFileSync(filePath, 'utf-8');
290
- const typeStart = content.indexOf(`export type ${typeName}`);
291
- if (typeStart !== -1) {
292
- const afterStart = content.substring(typeStart);
293
- const equalsIndex = afterStart.indexOf('=');
294
- if (equalsIndex !== -1) {
295
- const afterEquals = afterStart.substring(equalsIndex + 1).trimStart();
296
- if (afterEquals.startsWith('{')) {
297
- let braceCount = 0;
298
- let i = 0;
299
- let foundClose = false;
300
- for (; i < afterEquals.length; i++) {
301
- if (afterEquals[i] === '{') {
302
- braceCount++;
303
- }
304
- else if (afterEquals[i] === '}') {
305
- braceCount--;
306
- if (braceCount === 0) {
307
- foundClose = true;
308
- break;
309
- }
310
- }
311
- }
312
- if (foundClose) {
313
- const typeBody = afterEquals.substring(0, i + 1).trim();
314
- return typeBody;
315
- }
316
- }
317
- else {
318
- const semicolonIndex = afterEquals.indexOf(';');
319
- if (semicolonIndex !== -1) {
320
- return afterEquals.substring(0, semicolonIndex).trim();
321
- }
322
- }
323
- }
324
- }
325
- const interfaceStart = content.indexOf(`export interface ${typeName}`);
326
- if (interfaceStart !== -1) {
327
- const afterStart = content.substring(interfaceStart);
328
- const openBrace = afterStart.indexOf('{');
329
- if (openBrace !== -1) {
330
- let braceCount = 0;
331
- let i = openBrace;
332
- let foundClose = false;
333
- for (; i < afterStart.length; i++) {
334
- if (afterStart[i] === '{') {
335
- braceCount++;
336
- }
337
- else if (afterStart[i] === '}') {
338
- braceCount--;
339
- if (braceCount === 0) {
340
- foundClose = true;
341
- break;
342
- }
343
- }
344
- }
345
- if (foundClose) {
346
- const interfaceBody = afterStart.substring(openBrace + 1, i).trim();
347
- return `{ ${interfaceBody} }`;
348
- }
349
- }
350
- }
351
- return undefined;
352
- }
353
- catch {
354
- return undefined;
355
- }
356
- }
1
+ import { API_BASE_PATH } from '../../shared/constants.js';
2
+ import { DevServerState } from './dev-server-state.js';
3
+ import { createApiMiddleware, createSocketSetup } from './dev-server-middleware.js';
357
4
  const noopEmitter = {
358
5
  emit() { },
359
6
  };
360
- /**
361
- * Creates middleware for Vite development server
362
- */
363
7
  export function createViteDevServerMiddleware(options) {
364
8
  const state = new DevServerState(options);
365
- const ready = state.initialize().catch(error => {
9
+ const ready = state.initialize().catch((error) => {
366
10
  options.logger.error(`Failed to initialize Vitek: ${error instanceof Error ? error.message : String(error)}`);
367
11
  });
368
- const port = options.viteServer.config.server.port ?? 5173;
12
+ const port = options.viteServer.config.server?.port ?? 5173;
369
13
  const apiBaseUrl = `http://127.0.0.1:${port}${API_BASE_PATH}`;
370
14
  const api = {
371
15
  async fetch(path, fetchOptions) {
@@ -396,25 +40,9 @@ export function createViteDevServerMiddleware(options) {
396
40
  };
397
41
  return {
398
42
  ready,
399
- middleware: createRequestHandler({
400
- routes: state.routes,
401
- middlewares: state.middlewares,
402
- logger: options.logger,
403
- shared,
404
- }),
43
+ middleware: createApiMiddleware(state, options, shared),
405
44
  cleanup: () => state.cleanup(),
406
45
  reload: () => state.reload(),
407
- setupSockets: (httpServer) => {
408
- if (options.sockets !== false && state.sockets.length > 0) {
409
- const socketBasePath = options.socketBasePath ?? SOCKET_BASE_PATH;
410
- const handler = createSocketHandler({
411
- sockets: state.sockets,
412
- socketBasePath,
413
- shared,
414
- logger: options.logger,
415
- });
416
- httpServer.on('upgrade', handler);
417
- }
418
- },
46
+ setupSockets: createSocketSetup(state, options, shared),
419
47
  };
420
48
  }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Path normalization utilities for Vite plugin hooks.
3
+ * Centralizes logic for importer/module id resolution (file:, /, absolute).
4
+ */
5
+ /**
6
+ * Converts an importer string (from resolveId) to an absolute filesystem path.
7
+ * Handles file: URLs, leading-slash paths (virtual), and absolute paths.
8
+ */
9
+ export declare function normalizeImporterPath(importer: string, root: string): string;
10
+ /**
11
+ * Converts a module id (from transform/resolveId) to an absolute filesystem path.
12
+ * Handles file: URLs and leading-slash paths (virtual).
13
+ */
14
+ export declare function normalizeModuleIdPath(id: string, root: string): string;
15
+ /**
16
+ * Resolves a path to an existing file, trying extensions if the path has none.
17
+ * Returns the resolved absolute path or null if not found.
18
+ */
19
+ export declare function resolveWithExtension(filePath: string): string | null;
20
+ //# sourceMappingURL=path-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../../../src/adapters/vite/path-utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAS5E;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAOtE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKpE"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Path normalization utilities for Vite plugin hooks.
3
+ * Centralizes logic for importer/module id resolution (file:, /, absolute).
4
+ */
5
+ import * as path from 'path';
6
+ import * as fs from 'fs';
7
+ import { fileURLToPath } from 'url';
8
+ const EXTENSIONS = ['.ts', '.tsx', '.mts', '.js', '.jsx', '.mjs'];
9
+ /**
10
+ * Converts an importer string (from resolveId) to an absolute filesystem path.
11
+ * Handles file: URLs, leading-slash paths (virtual), and absolute paths.
12
+ */
13
+ export function normalizeImporterPath(importer, root) {
14
+ if (importer.startsWith('file:')) {
15
+ return fileURLToPath(importer);
16
+ }
17
+ if (importer.startsWith('/')) {
18
+ const virtualPath = path.join(root, importer.replace(/^\//, ''));
19
+ return fs.existsSync(virtualPath) ? virtualPath : path.resolve(importer);
20
+ }
21
+ return path.resolve(importer);
22
+ }
23
+ /**
24
+ * Converts a module id (from transform/resolveId) to an absolute filesystem path.
25
+ * Handles file: URLs and leading-slash paths (virtual).
26
+ */
27
+ export function normalizeModuleIdPath(id, root) {
28
+ const idPath = id.startsWith('file:') ? fileURLToPath(id) : id;
29
+ if (idPath.startsWith('/')) {
30
+ const virtualPath = path.join(root, idPath.replace(/^\//, ''));
31
+ return fs.existsSync(virtualPath) ? virtualPath : path.resolve(idPath);
32
+ }
33
+ return path.resolve(idPath);
34
+ }
35
+ /**
36
+ * Resolves a path to an existing file, trying extensions if the path has none.
37
+ * Returns the resolved absolute path or null if not found.
38
+ */
39
+ export function resolveWithExtension(filePath) {
40
+ if (fs.existsSync(filePath))
41
+ return filePath;
42
+ const ext = EXTENSIONS.find((e) => fs.existsSync(filePath + e));
43
+ if (ext)
44
+ return filePath + ext;
45
+ return null;
46
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=path-utils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-utils.test.d.ts","sourceRoot":"","sources":["../../../src/adapters/vite/path-utils.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,79 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { pathToFileURL } from 'url';
5
+ import { normalizeImporterPath, normalizeModuleIdPath, resolveWithExtension, } from './path-utils.js';
6
+ describe('path-utils', () => {
7
+ let rootDir;
8
+ beforeEach(() => {
9
+ rootDir = fs.mkdtempSync(path.join(process.cwd(), 'path-utils-test-'));
10
+ fs.mkdirSync(path.join(rootDir, 'src', 'api'), { recursive: true });
11
+ fs.mkdirSync(path.join(rootDir, 'src', 'lib'), { recursive: true });
12
+ fs.writeFileSync(path.join(rootDir, 'src', 'api', 'health.ts'), '', 'utf-8');
13
+ fs.writeFileSync(path.join(rootDir, 'src', 'lib', 'utils.ts'), '', 'utf-8');
14
+ });
15
+ afterEach(() => {
16
+ try {
17
+ fs.rmSync(rootDir, { recursive: true });
18
+ }
19
+ catch {
20
+ // ignore
21
+ }
22
+ });
23
+ describe('normalizeImporterPath', () => {
24
+ it('converts file: URL to absolute path', () => {
25
+ const filePath = path.join(rootDir, 'src', 'api', 'health.ts');
26
+ const fileUrl = pathToFileURL(filePath).href;
27
+ expect(normalizeImporterPath(fileUrl, rootDir)).toBe(filePath);
28
+ });
29
+ it('converts leading-slash path (virtual) when file exists', () => {
30
+ const virtualPath = '/src/api/health.ts';
31
+ const result = normalizeImporterPath(virtualPath, rootDir);
32
+ expect(result).toBe(path.join(rootDir, 'src', 'api', 'health.ts'));
33
+ });
34
+ it('converts leading-slash path when file does not exist', () => {
35
+ const virtualPath = '/nonexistent/file.ts';
36
+ const result = normalizeImporterPath(virtualPath, rootDir);
37
+ expect(result).toBe(path.resolve(virtualPath));
38
+ });
39
+ it('resolves absolute path', () => {
40
+ const absPath = path.join(rootDir, 'src', 'api', 'health.ts');
41
+ expect(normalizeImporterPath(absPath, rootDir)).toBe(path.resolve(absPath));
42
+ });
43
+ });
44
+ describe('normalizeModuleIdPath', () => {
45
+ it('converts file: URL to absolute path', () => {
46
+ const filePath = path.join(rootDir, 'src', 'lib', 'utils.ts');
47
+ const fileUrl = pathToFileURL(filePath).href;
48
+ expect(normalizeModuleIdPath(fileUrl, rootDir)).toBe(filePath);
49
+ });
50
+ it('converts leading-slash path (virtual) when file exists', () => {
51
+ const virtualPath = '/src/lib/utils.ts';
52
+ const result = normalizeModuleIdPath(virtualPath, rootDir);
53
+ expect(result).toBe(path.join(rootDir, 'src', 'lib', 'utils.ts'));
54
+ });
55
+ it('resolves path without leading slash', () => {
56
+ const relPath = path.join(rootDir, 'src', 'api', 'health.ts');
57
+ expect(normalizeModuleIdPath(relPath, rootDir)).toBe(path.resolve(relPath));
58
+ });
59
+ });
60
+ describe('resolveWithExtension', () => {
61
+ it('returns path when file exists without extension', () => {
62
+ const existingPath = path.join(rootDir, 'src', 'api', 'health.ts');
63
+ expect(resolveWithExtension(existingPath)).toBe(existingPath);
64
+ });
65
+ it('adds .ts when file exists with extension', () => {
66
+ const basePath = path.join(rootDir, 'src', 'api', 'health');
67
+ expect(resolveWithExtension(basePath)).toBe(basePath + '.ts');
68
+ });
69
+ it('adds .tsx when .ts does not exist but .tsx does', () => {
70
+ fs.writeFileSync(path.join(rootDir, 'src', 'component.tsx'), '', 'utf-8');
71
+ const basePath = path.join(rootDir, 'src', 'component');
72
+ expect(resolveWithExtension(basePath)).toBe(basePath + '.tsx');
73
+ });
74
+ it('returns null when file does not exist', () => {
75
+ const basePath = path.join(rootDir, 'src', 'nonexistent');
76
+ expect(resolveWithExtension(basePath)).toBeNull();
77
+ });
78
+ });
79
+ });
@@ -6,6 +6,7 @@ export interface BuildApiBundleOptions {
6
6
  root: string;
7
7
  apiDir: string;
8
8
  outDir: string;
9
+ alias?: Record<string, string>;
9
10
  }
10
11
  /**
11
12
  * Builds the API bundle and writes it to outDir/vitek-api.mjs
@@ -1 +1 @@
1
- {"version":3,"file":"build-api-bundle.d.ts","sourceRoot":"","sources":["../../src/build/build-api-bundle.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAgDD;;;GAGG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgD3F;AAED,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C"}
1
+ {"version":3,"file":"build-api-bundle.d.ts","sourceRoot":"","sources":["../../src/build/build-api-bundle.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAgFD;;;GAGG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmD3F;AAED,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C"}
@@ -22,7 +22,8 @@ function generateEntryContent(scanResult, entryDir) {
22
22
  scanResult.middlewares.forEach((mw, i) => {
23
23
  const rel = path.relative(entryDir, mw.path).replace(/\\/g, '/');
24
24
  const importPath = rel.startsWith('.') ? rel : `./${rel}`;
25
- lines.push(`import mw_${i} from ${JSON.stringify(importPath)};`);
25
+ // Use namespace import so we can read both default and named export 'config' (for global pathPatterns)
26
+ lines.push(`import * as mw_${i}_ns from ${JSON.stringify(importPath)};`);
26
27
  });
27
28
  const routeEntries = scanResult.routes.map((parsed, i) => {
28
29
  const regex = patternToRegex(parsed.pattern);
@@ -34,7 +35,11 @@ function generateEntryContent(scanResult, entryDir) {
34
35
  lines.push(routeEntries.join(',\n'));
35
36
  lines.push('];');
36
37
  const mwEntries = scanResult.middlewares.map((mw, i) => {
37
- return ` { basePattern: ${JSON.stringify(mw.basePattern)}, middleware: (() => { const m = mw_${i}; const fn = typeof m === 'function' || Array.isArray(m) ? m : (m.default ?? m.middleware); return Array.isArray(fn) ? fn : [fn]; })() }`;
38
+ const base = ` { basePattern: ${JSON.stringify(mw.basePattern)}, middleware: (() => { const m = mw_${i}_ns.default ?? mw_${i}_ns; const fn = typeof m === 'function' || Array.isArray(m) ? m : (m?.default ?? m?.middleware); return Array.isArray(fn) ? fn : [fn]; })()`;
39
+ if (mw.basePattern === '') {
40
+ return `${base}, pathPatterns: (() => { const c = mw_${i}_ns?.config; if (!c?.path) return undefined; const p = Array.isArray(c.path) ? c.path : [c.path]; return p.map((x) => String(x).replace(/^\\/api\\/?/, '').replace(/^\\/+|\\/+$/g, '')); })() }`;
41
+ }
42
+ return `${base} }`;
38
43
  });
39
44
  lines.push('');
40
45
  lines.push('const middlewares = [');
@@ -44,12 +49,39 @@ function generateEntryContent(scanResult, entryDir) {
44
49
  lines.push('export { routes, middlewares };');
45
50
  return lines.join('\n');
46
51
  }
52
+ function createAliasPlugin(root, alias) {
53
+ const entries = Object.entries(alias)
54
+ .filter(([, v]) => v != null && v !== '')
55
+ .sort(([a], [b]) => b.length - a.length);
56
+ return {
57
+ name: 'vitek-alias',
58
+ setup(build) {
59
+ if (entries.length === 0)
60
+ return;
61
+ const b = build;
62
+ const filter = new RegExp('^(' + entries.map(([k]) => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|') + ')(/|$)');
63
+ b.onResolve({ filter }, (args) => {
64
+ const imp = args.path;
65
+ for (const [key, value] of entries) {
66
+ if (imp === key || imp.startsWith(key + '/')) {
67
+ const rest = imp.slice(key.length).replace(/^\//, '') || '';
68
+ const base = path.resolve(root, value, rest);
69
+ const withExt = ['.js', '.ts', '.mjs', '.cjs'].find((ext) => fs.existsSync(base + ext));
70
+ const resolved = withExt ? base + withExt : base;
71
+ return { path: resolved };
72
+ }
73
+ }
74
+ return null;
75
+ });
76
+ },
77
+ };
78
+ }
47
79
  /**
48
80
  * Builds the API bundle and writes it to outDir/vitek-api.mjs
49
81
  * Returns the path to the written file, or null if skipped (no apiDir, no routes, or error)
50
82
  */
51
83
  export async function buildApiBundle(options) {
52
- const { root, apiDir, outDir } = options;
84
+ const { root, apiDir, outDir, alias } = options;
53
85
  if (!fs.existsSync(apiDir)) {
54
86
  return null;
55
87
  }
@@ -69,6 +101,7 @@ export async function buildApiBundle(options) {
69
101
  if (!esbuild) {
70
102
  return null;
71
103
  }
104
+ const plugins = alias && Object.keys(alias).length > 0 ? [createAliasPlugin(root, alias)] : [];
72
105
  try {
73
106
  await esbuild.build({
74
107
  entryPoints: [tmpEntry],
@@ -77,6 +110,8 @@ export async function buildApiBundle(options) {
77
110
  platform: 'node',
78
111
  outfile: outFile,
79
112
  external: ['vitek-plugin'],
113
+ minify: true,
114
+ plugins,
80
115
  });
81
116
  return outFile;
82
117
  }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=build-api-bundle.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-api-bundle.test.d.ts","sourceRoot":"","sources":["../../src/build/build-api-bundle.test.ts"],"names":[],"mappings":""}