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
package/dist/plugin.js CHANGED
@@ -1,249 +1,5 @@
1
1
  /**
2
2
  * Main Vite plugin
3
- * Thin layer that registers the plugin and connects with adapters
3
+ * Exports VitekOptions and vitek() aggregates sub-plugins
4
4
  */
5
- import * as path from 'path';
6
- import * as fs from 'fs';
7
- import { pathToFileURL, fileURLToPath } from 'url';
8
- import { createViteDevServerMiddleware } from './adapters/vite/dev-server.js';
9
- import { createViteLogger } from './adapters/vite/logger.js';
10
- import { createRequestHandler } from './core/server/request-handler.js';
11
- import { buildApiBundle, getApiBundleFilename } from './build/build-api-bundle.js';
12
- import { buildSocketsBundle, getSocketsBundleFilename } from './build/build-sockets-bundle.js';
13
- import { createSocketHandler } from './core/socket/socket-handler.js';
14
- import { API_BASE_PATH, API_DIR_NAME, getSocketBasePath } from './shared/constants.js';
15
- /**
16
- * Vite plugin for Vitek
17
- */
18
- export function vitek(options = {}) {
19
- const apiDirOption = options.apiDir || `src/${API_DIR_NAME}`;
20
- const buildApi = options.buildApi !== false;
21
- let root;
22
- let buildOutDir;
23
- let cleanupFn = null;
24
- return {
25
- name: 'vitek',
26
- enforce: 'pre',
27
- configResolved(config) {
28
- root = config.root;
29
- buildOutDir = path.resolve(root, config.build?.outDir ?? 'dist');
30
- },
31
- resolveId(id, importer) {
32
- if (!id.startsWith('.') || !importer)
33
- return null;
34
- const fullApiDir = path.resolve(root, apiDirOption);
35
- let importerPath;
36
- if (importer.startsWith('file:')) {
37
- importerPath = fileURLToPath(importer);
38
- }
39
- else if (importer.startsWith('/')) {
40
- const virtualPath = path.join(root, importer.replace(/^\//, ''));
41
- importerPath = fs.existsSync(virtualPath) ? virtualPath : path.resolve(importer);
42
- }
43
- else {
44
- importerPath = path.resolve(importer);
45
- }
46
- const normalizedApiDir = path.resolve(fullApiDir);
47
- const normalizedImporter = path.resolve(importerPath);
48
- if (!normalizedImporter.startsWith(normalizedApiDir))
49
- return null;
50
- let resolved = path.resolve(path.dirname(normalizedImporter), id);
51
- if (!fs.existsSync(resolved)) {
52
- const ext = ['.ts', '.tsx', '.mts', '.js', '.jsx', '.mjs'].find((e) => fs.existsSync(resolved + e));
53
- if (ext)
54
- resolved += ext;
55
- }
56
- return fs.existsSync(resolved) ? pathToFileURL(resolved).href : null;
57
- },
58
- transform(code, id) {
59
- const idPath = id.startsWith('file:') ? fileURLToPath(id) : id;
60
- const srcDir = path.resolve(root, 'src');
61
- const virtualCandidate = idPath.startsWith('/') ? path.join(root, idPath.replace(/^\//, '')) : null;
62
- const normalizedId = virtualCandidate != null && fs.existsSync(virtualCandidate)
63
- ? virtualCandidate
64
- : path.resolve(idPath);
65
- if (!normalizedId.startsWith(srcDir))
66
- return null;
67
- const dir = path.dirname(normalizedId);
68
- const rootSlash = path.resolve(root) + path.sep;
69
- const rewritten = code.replace(/from\s+['"](\.\.?[^'"]+)['"]/g, (match, specifier) => {
70
- const resolved = path.resolve(dir, specifier);
71
- if (!resolved.startsWith(rootSlash))
72
- return match;
73
- let target = resolved;
74
- if (!fs.existsSync(target)) {
75
- const ext = ['.ts', '.tsx', '.mts', '.js', '.jsx', '.mjs'].find((e) => fs.existsSync(target + e));
76
- if (ext)
77
- target += ext;
78
- }
79
- if (!fs.existsSync(target))
80
- return match;
81
- const rootRelative = path.relative(root, target).replace(/\\/g, '/');
82
- const newSpecifier = `/${rootRelative}`;
83
- const quote = match.includes('"') ? '"' : "'";
84
- return `from ${quote}${newSpecifier}${quote}`;
85
- });
86
- return rewritten !== code ? { code: rewritten, map: null } : null;
87
- },
88
- async configureServer(server) {
89
- const fullApiDir = path.resolve(root, apiDirOption);
90
- if (!fs.existsSync(fullApiDir)) {
91
- server.config.logger.warn(`[vitek] API directory not found: ${fullApiDir}`);
92
- return;
93
- }
94
- const logger = createViteLogger(server.config.logger, options.logging);
95
- const socketsEnabled = options.sockets !== false;
96
- const socketBasePath = getSocketBasePath(options.apiBasePath, typeof options.sockets === 'object' ? options.sockets?.path : undefined);
97
- const { ready, middleware, cleanup, setupSockets } = createViteDevServerMiddleware({
98
- root,
99
- apiDir: fullApiDir,
100
- logger,
101
- viteServer: server,
102
- enableValidation: options.enableValidation || false,
103
- openApi: options.openApi,
104
- sockets: socketsEnabled,
105
- socketBasePath,
106
- });
107
- cleanupFn = cleanup;
108
- server.middlewares.use(middleware);
109
- await ready;
110
- if (socketsEnabled && server.httpServer) {
111
- setupSockets(server.httpServer);
112
- }
113
- logger.info('Vitek plugin initialized');
114
- const port = server.config.server?.port ?? 5173;
115
- const apiPath = options.apiBasePath ?? API_BASE_PATH;
116
- const originalPrintUrls = server.printUrls?.bind(server);
117
- if (typeof originalPrintUrls === 'function') {
118
- server.printUrls = () => {
119
- originalPrintUrls();
120
- const host = 'localhost';
121
- const apiUrl = `http://${host}:${port}${apiPath}`;
122
- server.config.logger.info(` ➜ API: ${apiUrl}`);
123
- if (socketsEnabled) {
124
- const wsUrl = `ws://${host}:${port}${socketBasePath}`;
125
- server.config.logger.info(` ➜ WS: ${wsUrl}`);
126
- }
127
- };
128
- }
129
- },
130
- async configurePreviewServer(server) {
131
- if (!buildApi) {
132
- return;
133
- }
134
- const bundlePath = path.join(buildOutDir, getApiBundleFilename());
135
- if (!fs.existsSync(bundlePath)) {
136
- server.config.logger.warn('[vitek] API bundle not found; preview serving static assets only. Run `vite build` first.');
137
- return;
138
- }
139
- const previewPort = server.config.preview?.port ?? 4173;
140
- const apiBaseUrl = `http://127.0.0.1:${previewPort}${API_BASE_PATH}`;
141
- const api = {
142
- async fetch(path, fetchOptions) {
143
- const url = `${apiBaseUrl}/${path.replace(/^\//, '')}`;
144
- const res = await fetch(url, {
145
- method: fetchOptions?.method ?? 'GET',
146
- headers: fetchOptions?.body !== undefined
147
- ? { 'Content-Type': 'application/json' }
148
- : undefined,
149
- body: fetchOptions?.body !== undefined
150
- ? JSON.stringify(fetchOptions.body)
151
- : undefined,
152
- });
153
- const text = await res.text();
154
- if (!text)
155
- return undefined;
156
- try {
157
- return JSON.parse(text);
158
- }
159
- catch {
160
- return text;
161
- }
162
- },
163
- };
164
- const noopSockets = { emit() { } };
165
- const shared = { sockets: noopSockets, api };
166
- const bundleUrl = pathToFileURL(bundlePath).href;
167
- const bundleLoadPromise = import(bundleUrl);
168
- let apiHandler = null;
169
- const apiMiddleware = (req, res, next) => {
170
- const pathname = req.url?.split('?')[0] ?? '';
171
- if (pathname !== API_BASE_PATH && !pathname.startsWith(API_BASE_PATH + '/')) {
172
- return next();
173
- }
174
- bundleLoadPromise
175
- .then((mod) => {
176
- if (!apiHandler) {
177
- apiHandler = createRequestHandler({
178
- routes: mod.routes,
179
- middlewares: mod.middlewares,
180
- shared,
181
- });
182
- server.config.logger.info('[vitek] API middleware registered for preview');
183
- }
184
- apiHandler(req, res, next);
185
- })
186
- .catch((err) => {
187
- server.config.logger.error(`[vitek] Failed to load API bundle: ${err instanceof Error ? err.message : String(err)}`);
188
- res.statusCode = 500;
189
- res.setHeader('Content-Type', 'application/json');
190
- res.end(JSON.stringify({ error: 'Internal server error', message: 'Failed to load API bundle' }));
191
- });
192
- };
193
- server.middlewares.use(apiMiddleware);
194
- const socketsEnabled = options.sockets !== false;
195
- const socketBasePath = getSocketBasePath(options.apiBasePath, typeof options.sockets === 'object' ? options.sockets?.path : undefined);
196
- const socketsBundlePath = path.join(buildOutDir, getSocketsBundleFilename());
197
- if (socketsEnabled && fs.existsSync(socketsBundlePath)) {
198
- try {
199
- const socketsUrl = pathToFileURL(socketsBundlePath).href;
200
- const mod = await import(socketsUrl);
201
- const handler = createSocketHandler({
202
- sockets: mod.sockets,
203
- socketBasePath,
204
- shared,
205
- });
206
- server.httpServer?.on('upgrade', handler);
207
- server.config.logger.info('[vitek] WebSocket sockets registered for preview');
208
- }
209
- catch (err) {
210
- server.config.logger.warn(`[vitek] Failed to load sockets bundle: ${err instanceof Error ? err.message : String(err)}`);
211
- }
212
- }
213
- },
214
- async closeBundle() {
215
- if (buildApi) {
216
- const fullApiDir = path.resolve(root, apiDirOption);
217
- try {
218
- await buildApiBundle({
219
- root,
220
- apiDir: fullApiDir,
221
- outDir: buildOutDir,
222
- });
223
- }
224
- catch (err) {
225
- console.error('[vitek] Failed to build API bundle:', err instanceof Error ? err.message : err);
226
- }
227
- const socketsEnabled = options.sockets !== false;
228
- if (socketsEnabled && fs.existsSync(fullApiDir)) {
229
- try {
230
- await buildSocketsBundle({
231
- root,
232
- apiDir: fullApiDir,
233
- outDir: buildOutDir,
234
- });
235
- }
236
- catch (err) {
237
- console.error('[vitek] Failed to build sockets bundle:', err instanceof Error ? err.message : err);
238
- }
239
- }
240
- }
241
- },
242
- buildEnd() {
243
- if (cleanupFn) {
244
- cleanupFn();
245
- cleanupFn = null;
246
- }
247
- },
248
- };
249
- }
5
+ export { vitek } from './plugin/vitek.js';
@@ -3,7 +3,12 @@ import * as fs from 'fs';
3
3
  import * as path from 'path';
4
4
  import { pathToFileURL } from 'url';
5
5
  import { vitek } from './plugin.js';
6
+ function findPlugin(plugins, name) {
7
+ return plugins.find((p) => p.name === name);
8
+ }
6
9
  function callResolveId(plugin, id, importer) {
10
+ if (!plugin)
11
+ return null;
7
12
  const hook = plugin.resolveId;
8
13
  if (!hook)
9
14
  return null;
@@ -12,6 +17,8 @@ function callResolveId(plugin, id, importer) {
12
17
  return fn.call(null, id, importer, { attributes: {}, isEntry: false });
13
18
  }
14
19
  function callTransform(plugin, code, id) {
20
+ if (!plugin)
21
+ return null;
15
22
  const hook = plugin.transform;
16
23
  if (!hook)
17
24
  return null;
@@ -20,9 +27,13 @@ function callTransform(plugin, code, id) {
20
27
  return fn.call(null, code, id);
21
28
  }
22
29
  describe('vitek plugin resolveId and transform', () => {
23
- let plugin;
30
+ let plugins;
24
31
  let rootDir;
25
32
  let apiDir;
33
+ let configPlugin;
34
+ let resolvePlugin;
35
+ let transformPlugin;
36
+ let buildPlugin;
26
37
  beforeEach(() => {
27
38
  rootDir = fs.mkdtempSync(path.join(process.cwd(), 'vitek-plugin-test-'));
28
39
  apiDir = path.join(rootDir, 'src', 'api');
@@ -32,11 +43,16 @@ describe('vitek plugin resolveId and transform', () => {
32
43
  fs.writeFileSync(path.join(rootDir, 'src', 'lib', 'greeting.ts'), "export function getGreeting() { return 'hi'; }\n", 'utf-8');
33
44
  fs.mkdirSync(path.join(rootDir, 'src', 'api', 'nested'), { recursive: true });
34
45
  fs.writeFileSync(path.join(rootDir, 'src', 'api', 'nested', 'deep.get.ts'), "import { getGreeting } from '../../lib/greeting';\nexport default function handler() { return {}; }\n", 'utf-8');
35
- plugin = vitek({ apiDir: path.join('src', 'api') });
36
- plugin.configResolved?.({
37
- root: rootDir,
38
- build: { outDir: 'dist' },
39
- });
46
+ plugins = vitek({ apiDir: path.join('src', 'api') });
47
+ configPlugin = findPlugin(plugins, 'vitek:build');
48
+ resolvePlugin = findPlugin(plugins, 'vitek:resolve');
49
+ transformPlugin = findPlugin(plugins, 'vitek:transform');
50
+ buildPlugin = findPlugin(plugins, 'vitek:build');
51
+ const configResolvedHook = configPlugin.configResolved;
52
+ if (configResolvedHook) {
53
+ const fn = typeof configResolvedHook === 'function' ? configResolvedHook : configResolvedHook.handler;
54
+ fn.call(null, { root: rootDir, build: { outDir: 'dist' } });
55
+ }
40
56
  });
41
57
  afterEach(() => {
42
58
  try {
@@ -49,19 +65,19 @@ describe('vitek plugin resolveId and transform', () => {
49
65
  describe('resolveId', () => {
50
66
  it('returns null when id does not start with .', () => {
51
67
  const importer = pathToFileURL(path.join(apiDir, 'health.get.ts')).href;
52
- expect(callResolveId(plugin, 'vue', importer)).toBeNull();
53
- expect(callResolveId(plugin, '/absolute', importer)).toBeNull();
68
+ expect(callResolveId(resolvePlugin, 'vue', importer)).toBeNull();
69
+ expect(callResolveId(resolvePlugin, '/absolute', importer)).toBeNull();
54
70
  });
55
71
  it('returns null when importer is undefined', () => {
56
- expect(callResolveId(plugin, '../lib/greeting', undefined)).toBeNull();
72
+ expect(callResolveId(resolvePlugin, '../lib/greeting', undefined)).toBeNull();
57
73
  });
58
74
  it('returns null when importer is outside apiDir', () => {
59
75
  const importerOutside = pathToFileURL(path.join(rootDir, 'src', 'main.ts')).href;
60
- expect(callResolveId(plugin, '../lib/greeting', importerOutside)).toBeNull();
76
+ expect(callResolveId(resolvePlugin, '../lib/greeting', importerOutside)).toBeNull();
61
77
  });
62
78
  it('resolves relative import from api file to existing file and returns file URL', () => {
63
79
  const importer = pathToFileURL(path.join(apiDir, 'health.get.ts')).href;
64
- const result = callResolveId(plugin, '../lib/greeting', importer);
80
+ const result = callResolveId(resolvePlugin, '../lib/greeting', importer);
65
81
  expect(result).not.toBeNull();
66
82
  expect(typeof result).toBe('string');
67
83
  expect(result).toContain('greeting');
@@ -69,19 +85,19 @@ describe('vitek plugin resolveId and transform', () => {
69
85
  });
70
86
  it('resolves with extension fallback when target has no extension', () => {
71
87
  const importer = pathToFileURL(path.join(apiDir, 'health.get.ts')).href;
72
- const result = callResolveId(plugin, '../lib/greeting', importer);
88
+ const result = callResolveId(resolvePlugin, '../lib/greeting', importer);
73
89
  expect(result).not.toBeNull();
74
90
  const filePath = result.replace(/^file:\/\//, '').replace(/%2F/g, '/').replace(/%3A/g, ':');
75
91
  expect(fs.existsSync(filePath) || fs.existsSync(filePath + '.ts')).toBe(true);
76
92
  });
77
93
  it('returns null when relative target does not exist', () => {
78
94
  const importer = pathToFileURL(path.join(apiDir, 'health.get.ts')).href;
79
- expect(callResolveId(plugin, '../lib/nonexistent', importer)).toBeNull();
95
+ expect(callResolveId(resolvePlugin, '../lib/nonexistent', importer)).toBeNull();
80
96
  });
81
97
  it('resolves nested api file relative import', () => {
82
98
  const nestedDir = path.join(apiDir, 'nested');
83
99
  const importer = pathToFileURL(path.join(nestedDir, 'deep.get.ts')).href;
84
- const result = callResolveId(plugin, '../../lib/greeting', importer);
100
+ const result = callResolveId(resolvePlugin, '../../lib/greeting', importer);
85
101
  expect(result).not.toBeNull();
86
102
  expect(result).toContain('greeting');
87
103
  });
@@ -91,14 +107,14 @@ describe('vitek plugin resolveId and transform', () => {
91
107
  const code = "import x from '../lib/greeting';";
92
108
  fs.mkdirSync(path.join(rootDir, 'other'), { recursive: true });
93
109
  const idOutside = pathToFileURL(path.join(rootDir, 'other', 'main.ts')).href;
94
- const result = callTransform(plugin, code, idOutside);
110
+ const result = callTransform(transformPlugin, code, idOutside);
95
111
  expect(result).toBeNull();
96
112
  });
97
113
  it('rewrites relative import when id is under src/lib (not only api)', () => {
98
114
  fs.writeFileSync(path.join(rootDir, 'src', 'lib', 'executor.ts'), "import { getGreeting } from './greeting';\nexport function run() { return getGreeting(); }\n", 'utf-8');
99
115
  const code = "import { getGreeting } from './greeting';\nexport function run() { return getGreeting(); }\n";
100
116
  const id = pathToFileURL(path.join(rootDir, 'src', 'lib', 'executor.ts')).href;
101
- const result = callTransform(plugin, code, id);
117
+ const result = callTransform(transformPlugin, code, id);
102
118
  expect(result).not.toBeNull();
103
119
  expect(result.code).toContain("/src/lib/greeting");
104
120
  expect(result.code).not.toContain("from './greeting'");
@@ -109,14 +125,14 @@ describe('vitek plugin resolveId and transform', () => {
109
125
  fs.writeFileSync(path.join(rootDir, 'src', 'lib', 'nested', 'index.ts'), "export { getMessage } from './helper';\n", 'utf-8');
110
126
  const code = "export { getMessage } from './helper';\n";
111
127
  const id = pathToFileURL(path.join(rootDir, 'src', 'lib', 'nested', 'index.ts')).href;
112
- const result = callTransform(plugin, code, id);
128
+ const result = callTransform(transformPlugin, code, id);
113
129
  expect(result).not.toBeNull();
114
130
  expect(result.code).toContain("/src/lib/nested/helper");
115
131
  });
116
132
  it('rewrites relative import to root-relative path when id is under apiDir', () => {
117
133
  const code = "import { getGreeting } from '../lib/greeting';\nexport default function handler() {}";
118
134
  const id = pathToFileURL(path.join(apiDir, 'health.get.ts')).href;
119
- const result = callTransform(plugin, code, id);
135
+ const result = callTransform(transformPlugin, code, id);
120
136
  expect(result).not.toBeNull();
121
137
  expect(result.code).toContain("/src/lib/greeting");
122
138
  expect(result.code).not.toContain("from '../lib/greeting'");
@@ -124,44 +140,114 @@ describe('vitek plugin resolveId and transform', () => {
124
140
  it('preserves double quotes when original uses double quotes', () => {
125
141
  const code = 'import { getGreeting } from "../lib/greeting";';
126
142
  const id = pathToFileURL(path.join(apiDir, 'health.get.ts')).href;
127
- const result = callTransform(plugin, code, id);
143
+ const result = callTransform(transformPlugin, code, id);
128
144
  expect(result).not.toBeNull();
129
145
  expect(result.code).toContain('/src/lib/greeting');
130
146
  });
131
147
  it('returns null when code has no relative imports', () => {
132
148
  const code = "import vue from 'vue';\nexport default {}";
133
149
  const id = pathToFileURL(path.join(apiDir, 'health.get.ts')).href;
134
- const result = callTransform(plugin, code, id);
150
+ const result = callTransform(transformPlugin, code, id);
135
151
  expect(result).toBeNull();
136
152
  });
137
153
  it('rewrites nested relative import (../../lib)', () => {
138
154
  const code = "import { getGreeting } from '../../lib/greeting';";
139
155
  const nestedPath = path.join(apiDir, 'nested', 'deep.get.ts');
140
156
  const id = pathToFileURL(nestedPath).href;
141
- const result = callTransform(plugin, code, id);
157
+ const result = callTransform(transformPlugin, code, id);
142
158
  expect(result).not.toBeNull();
143
159
  expect(result.code).toContain("/src/lib/greeting");
144
160
  });
145
161
  it('does not rewrite import when resolved target is outside root', () => {
146
162
  const code = "import x from '../../../etc/passwd';";
147
163
  const id = pathToFileURL(path.join(apiDir, 'health.get.ts')).href;
148
- const result = callTransform(plugin, code, id);
164
+ const result = callTransform(transformPlugin, code, id);
149
165
  expect(result).toBeNull();
150
166
  });
151
167
  it('returns null when relative target file does not exist', () => {
152
168
  const code = "import x from '../lib/nonexistent';";
153
169
  const id = pathToFileURL(path.join(apiDir, 'health.get.ts')).href;
154
- const result = callTransform(plugin, code, id);
170
+ const result = callTransform(transformPlugin, code, id);
155
171
  expect(result).toBeNull();
156
172
  });
157
173
  });
158
174
  describe('plugin shape', () => {
159
- it('has enforce pre', () => {
160
- expect(plugin.enforce).toBe('pre');
175
+ it('returns array of sub-plugins with enforce pre for resolve/transform/build', () => {
176
+ expect(resolvePlugin.enforce).toBe('pre');
177
+ expect(transformPlugin.enforce).toBe('pre');
178
+ expect(buildPlugin.enforce).toBe('pre');
179
+ });
180
+ it('exposes resolveId and transform on respective plugins', () => {
181
+ expect(resolvePlugin.resolveId).toBeDefined();
182
+ expect(transformPlugin.transform).toBeDefined();
161
183
  });
162
- it('exposes resolveId and transform', () => {
163
- expect(plugin.resolveId).toBeDefined();
164
- expect(plugin.transform).toBeDefined();
184
+ });
185
+ describe('buildStart', () => {
186
+ it('generates api.services.ts and api.types.ts when buildStart runs', async () => {
187
+ fs.writeFileSync(path.join(rootDir, 'tsconfig.json'), '{}', 'utf-8');
188
+ const buildStart = buildPlugin.buildStart;
189
+ if (!buildStart) {
190
+ expect.fail('Plugin does not have buildStart hook');
191
+ }
192
+ await buildStart.call(null);
193
+ const servicesPath = path.join(rootDir, 'src', 'api.services.ts');
194
+ const typesPath = path.join(rootDir, 'src', 'api.types.ts');
195
+ expect(fs.existsSync(servicesPath)).toBe(true);
196
+ expect(fs.existsSync(typesPath)).toBe(true);
197
+ expect(fs.readFileSync(servicesPath, 'utf-8')).toContain('getHealth');
198
+ expect(fs.readFileSync(typesPath, 'utf-8')).toContain('VitekParams');
199
+ });
200
+ it('skips type generation and API bundle when buildApi is false', async () => {
201
+ fs.writeFileSync(path.join(rootDir, 'tsconfig.json'), '{}', 'utf-8');
202
+ const noApiPlugins = vitek({ apiDir: path.join('src', 'api'), buildApi: false });
203
+ const noApiBuild = findPlugin(noApiPlugins, 'vitek:build');
204
+ const configResolved = noApiBuild.configResolved;
205
+ if (configResolved) {
206
+ const fn = typeof configResolved === 'function' ? configResolved : configResolved.handler;
207
+ fn.call(null, { root: rootDir, build: { outDir: 'dist' } });
208
+ }
209
+ const buildStart = noApiBuild.buildStart;
210
+ const closeBundle = noApiBuild.closeBundle;
211
+ if (buildStart)
212
+ await buildStart.call(null);
213
+ fs.mkdirSync(path.join(rootDir, 'dist'), { recursive: true });
214
+ if (closeBundle)
215
+ await closeBundle.call(null);
216
+ expect(fs.existsSync(path.join(rootDir, 'src', 'api.types.ts'))).toBe(false);
217
+ expect(fs.existsSync(path.join(rootDir, 'src', 'api.services.ts'))).toBe(false);
218
+ expect(fs.existsSync(path.join(rootDir, 'dist', 'vitek-api.mjs'))).toBe(false);
165
219
  });
166
220
  });
221
+ describe('srcDir option', () => {
222
+ it('transform respects srcDir when custom srcDir is set', () => {
223
+ fs.mkdirSync(path.join(rootDir, 'lib', 'shared'), { recursive: true });
224
+ fs.writeFileSync(path.join(rootDir, 'lib', 'shared', 'helper.ts'), "export function help() { return 'ok'; }\n", 'utf-8');
225
+ fs.writeFileSync(path.join(rootDir, 'lib', 'entry.ts'), "import { help } from './shared/helper';\nexport default help;\n", 'utf-8');
226
+ const customPlugins = vitek({ apiDir: path.join('src', 'api'), srcDir: 'lib' });
227
+ const customTransform = findPlugin(customPlugins, 'vitek:transform');
228
+ const customBuild = findPlugin(customPlugins, 'vitek:build');
229
+ const configResolved = customBuild.configResolved;
230
+ if (configResolved) {
231
+ const fn = typeof configResolved === 'function' ? configResolved : configResolved.handler;
232
+ fn.call(null, { root: rootDir, build: { outDir: 'dist' } });
233
+ }
234
+ const code = "import { help } from './shared/helper';\nexport default help;\n";
235
+ const id = pathToFileURL(path.join(rootDir, 'lib', 'entry.ts')).href;
236
+ const result = callTransform(customTransform, code, id);
237
+ expect(result).not.toBeNull();
238
+ expect(result.code).toContain('/lib/shared/helper');
239
+ });
240
+ });
241
+ });
242
+ describe('vitek plugin options (cors, trustProxy, onError)', () => {
243
+ it('accepts cors and trustProxy and returns plugins', () => {
244
+ const plugins = vitek({ apiDir: 'src/api', cors: true, trustProxy: true });
245
+ expect(plugins.length).toBeGreaterThan(0);
246
+ expect(findPlugin(plugins, 'vitek:dev')).toBeDefined();
247
+ });
248
+ it('accepts onError option', () => {
249
+ const onError = () => { };
250
+ const plugins = vitek({ apiDir: 'src/api', onError });
251
+ expect(plugins.length).toBeGreaterThan(0);
252
+ });
167
253
  });
@@ -58,4 +58,25 @@ export declare function internalServerError(body?: any, headers?: Record<string,
58
58
  * Creates a redirect response (301, 302, 307, 308)
59
59
  */
60
60
  export declare function redirect(url: string, permanent?: boolean, preserveMethod?: boolean): VitekResponse;
61
+ /**
62
+ * Creates a plain text response (Content-Type: text/plain).
63
+ */
64
+ export declare function text(body: string, status?: number): VitekResponse;
65
+ /**
66
+ * Creates an HTML response (Content-Type: text/html).
67
+ */
68
+ export declare function html(body: string, status?: number): VitekResponse;
69
+ /**
70
+ * Cache-Control header helpers. Merge returned headers into your response, e.g.:
71
+ * `{ ...ok(body), headers: { ...ok(body).headers, ...cacheControl(60) } }`
72
+ */
73
+ export declare function cacheControl(maxAgeSeconds: number, options?: {
74
+ staleWhileRevalidate?: number;
75
+ private?: boolean;
76
+ }): Record<string, string>;
77
+ /**
78
+ * Returns headers to disable caching. Merge into response headers, e.g.:
79
+ * `{ ...ok(body), headers: { ...ok(body).headers, ...noStore() } }`
80
+ */
81
+ export declare function noStore(): Record<string, string>;
61
82
  //# sourceMappingURL=response-helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"response-helpers.d.ts","sourceRoot":"","sources":["../../src/shared/response-helpers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAEvE;;GAEG;AACH,wBAAgB,IAAI,CAClB,IAAI,EAAE,GAAG,EACT,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAO,GAClE,aAAa,CASf;AAED;;GAEG;AACH,wBAAgB,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,aAAa,CAE7E;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,aAAa,CAElF;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,aAAa,CAMzE;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,GAAE,GAA8B,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,aAAa,CAEhH;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,GAAE,GAA+B,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,aAAa,CAEnH;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,GAAE,GAA4B,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,aAAa,CAE7G;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,IAAI,GAAE,GAA4B,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,aAAa,CAE5G;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,IAAI,GAAE,GAA2B,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,aAAa,CAE3G;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,GAAE,GAAmC,EACzC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,aAAa,CAEf;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,IAAI,GAAE,GAAoC,EAC1C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,aAAa,CAEf;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,GAAE,GAAwC,EAC9C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,aAAa,CAEf;AAED;;GAEG;AACH,wBAAgB,QAAQ,CACtB,GAAG,EAAE,MAAM,EACX,SAAS,GAAE,OAAe,EAC1B,cAAc,GAAE,OAAe,GAC9B,aAAa,CASf"}
1
+ {"version":3,"file":"response-helpers.d.ts","sourceRoot":"","sources":["../../src/shared/response-helpers.ts"],"names":[],"mappings":"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"}
@@ -98,3 +98,44 @@ export function redirect(url, permanent = false, preserveMethod = false) {
98
98
  body: undefined,
99
99
  };
100
100
  }
101
+ /**
102
+ * Creates a plain text response (Content-Type: text/plain).
103
+ */
104
+ export function text(body, status = 200) {
105
+ return {
106
+ status,
107
+ headers: { 'Content-Type': 'text/plain; charset=utf-8' },
108
+ body,
109
+ };
110
+ }
111
+ /**
112
+ * Creates an HTML response (Content-Type: text/html).
113
+ */
114
+ export function html(body, status = 200) {
115
+ return {
116
+ status,
117
+ headers: { 'Content-Type': 'text/html; charset=utf-8' },
118
+ body,
119
+ };
120
+ }
121
+ /**
122
+ * Cache-Control header helpers. Merge returned headers into your response, e.g.:
123
+ * `{ ...ok(body), headers: { ...ok(body).headers, ...cacheControl(60) } }`
124
+ */
125
+ export function cacheControl(maxAgeSeconds, options) {
126
+ const parts = [`max-age=${maxAgeSeconds}`];
127
+ if (options?.staleWhileRevalidate != null) {
128
+ parts.push(`stale-while-revalidate=${options.staleWhileRevalidate}`);
129
+ }
130
+ if (options?.private === true) {
131
+ parts.push('private');
132
+ }
133
+ return { 'Cache-Control': parts.join(', ') };
134
+ }
135
+ /**
136
+ * Returns headers to disable caching. Merge into response headers, e.g.:
137
+ * `{ ...ok(body), headers: { ...ok(body).headers, ...noStore() } }`
138
+ */
139
+ export function noStore() {
140
+ return { 'Cache-Control': 'no-store' };
141
+ }