vitek-plugin 0.1.2-beta.6 → 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 (166) 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 -216
  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.test.d.ts +2 -0
  51. package/dist/core/asyncapi/generate.test.d.ts.map +1 -0
  52. package/dist/core/asyncapi/generate.test.js +120 -0
  53. package/dist/core/context/create-context.d.ts +2 -0
  54. package/dist/core/context/create-context.d.ts.map +1 -1
  55. package/dist/core/file-system/watch-api-dir.d.ts +4 -1
  56. package/dist/core/file-system/watch-api-dir.d.ts.map +1 -1
  57. package/dist/core/file-system/watch-api-dir.js +31 -6
  58. package/dist/core/file-system/watch-api-dir.test.d.ts +2 -0
  59. package/dist/core/file-system/watch-api-dir.test.d.ts.map +1 -0
  60. package/dist/core/file-system/watch-api-dir.test.js +38 -0
  61. package/dist/core/generation/run-file-generation.d.ts +2 -0
  62. package/dist/core/generation/run-file-generation.d.ts.map +1 -1
  63. package/dist/core/generation/run-file-generation.js +4 -1
  64. package/dist/core/introspection/manifest.d.ts +24 -0
  65. package/dist/core/introspection/manifest.d.ts.map +1 -0
  66. package/dist/core/introspection/manifest.js +41 -0
  67. package/dist/core/introspection/manifest.test.d.ts +2 -0
  68. package/dist/core/introspection/manifest.test.d.ts.map +1 -0
  69. package/dist/core/introspection/manifest.test.js +62 -0
  70. package/dist/core/middleware/get-applicable-middlewares.d.ts +7 -0
  71. package/dist/core/middleware/get-applicable-middlewares.d.ts.map +1 -1
  72. package/dist/core/middleware/get-applicable-middlewares.js +23 -15
  73. package/dist/core/middleware/get-applicable-middlewares.test.js +36 -1
  74. package/dist/core/openapi/generate.d.ts +3 -79
  75. package/dist/core/openapi/generate.d.ts.map +1 -1
  76. package/dist/core/openapi/generate.js +4 -419
  77. package/dist/core/openapi/generate.test.d.ts +2 -0
  78. package/dist/core/openapi/generate.test.d.ts.map +1 -0
  79. package/dist/core/openapi/generate.test.js +184 -0
  80. package/dist/core/openapi/jsdoc.d.ts +3 -0
  81. package/dist/core/openapi/jsdoc.d.ts.map +1 -0
  82. package/dist/core/openapi/jsdoc.js +68 -0
  83. package/dist/core/openapi/jsdoc.test.d.ts +2 -0
  84. package/dist/core/openapi/jsdoc.test.d.ts.map +1 -0
  85. package/dist/core/openapi/jsdoc.test.js +111 -0
  86. package/dist/core/openapi/spec-builder.d.ts +4 -0
  87. package/dist/core/openapi/spec-builder.d.ts.map +1 -0
  88. package/dist/core/openapi/spec-builder.js +257 -0
  89. package/dist/core/openapi/spec-builder.test.d.ts +2 -0
  90. package/dist/core/openapi/spec-builder.test.d.ts.map +1 -0
  91. package/dist/core/openapi/spec-builder.test.js +93 -0
  92. package/dist/core/openapi/types.d.ts +42 -0
  93. package/dist/core/openapi/types.d.ts.map +1 -0
  94. package/dist/core/openapi/types.js +5 -0
  95. package/dist/core/server/cors.d.ts +29 -0
  96. package/dist/core/server/cors.d.ts.map +1 -0
  97. package/dist/core/server/cors.js +55 -0
  98. package/dist/core/server/cors.test.d.ts +2 -0
  99. package/dist/core/server/cors.test.d.ts.map +1 -0
  100. package/dist/core/server/cors.test.js +49 -0
  101. package/dist/core/server/proxy.d.ts +16 -0
  102. package/dist/core/server/proxy.d.ts.map +1 -0
  103. package/dist/core/server/proxy.js +20 -0
  104. package/dist/core/server/proxy.test.d.ts +2 -0
  105. package/dist/core/server/proxy.test.d.ts.map +1 -0
  106. package/dist/core/server/proxy.test.js +53 -0
  107. package/dist/core/server/request-handler.d.ts +17 -3
  108. package/dist/core/server/request-handler.d.ts.map +1 -1
  109. package/dist/core/server/request-handler.js +192 -84
  110. package/dist/core/server/request-handler.test.js +287 -22
  111. package/dist/core/socket/socket-handler.test.d.ts +2 -0
  112. package/dist/core/socket/socket-handler.test.d.ts.map +1 -0
  113. package/dist/core/socket/socket-handler.test.js +107 -0
  114. package/dist/core/types/schema.test.d.ts +2 -0
  115. package/dist/core/types/schema.test.d.ts.map +1 -0
  116. package/dist/core/types/schema.test.js +41 -0
  117. package/dist/core/validation/types.d.ts +2 -1
  118. package/dist/core/validation/types.d.ts.map +1 -1
  119. package/dist/core/validation/validator.d.ts +4 -16
  120. package/dist/core/validation/validator.d.ts.map +1 -1
  121. package/dist/core/validation/validator.js +4 -16
  122. package/dist/index.d.ts +6 -1
  123. package/dist/index.d.ts.map +1 -1
  124. package/dist/index.js +2 -1
  125. package/dist/plugin/context.d.ts +15 -0
  126. package/dist/plugin/context.d.ts.map +1 -0
  127. package/dist/plugin/context.js +12 -0
  128. package/dist/plugin/options.d.ts +46 -0
  129. package/dist/plugin/options.d.ts.map +1 -0
  130. package/dist/plugin/options.js +1 -0
  131. package/dist/plugin/plugin-api.d.ts +49 -0
  132. package/dist/plugin/plugin-api.d.ts.map +1 -0
  133. package/dist/plugin/plugin-api.js +5 -0
  134. package/dist/plugin/vitek-build.d.ts +7 -0
  135. package/dist/plugin/vitek-build.d.ts.map +1 -0
  136. package/dist/plugin/vitek-build.js +104 -0
  137. package/dist/plugin/vitek-config.d.ts +4 -0
  138. package/dist/plugin/vitek-config.d.ts.map +1 -0
  139. package/dist/plugin/vitek-config.js +51 -0
  140. package/dist/plugin/vitek-config.test.d.ts +2 -0
  141. package/dist/plugin/vitek-config.test.d.ts.map +1 -0
  142. package/dist/plugin/vitek-config.test.js +62 -0
  143. package/dist/plugin/vitek-dev.d.ts +7 -0
  144. package/dist/plugin/vitek-dev.d.ts.map +1 -0
  145. package/dist/plugin/vitek-dev.js +71 -0
  146. package/dist/plugin/vitek-preview.d.ts +7 -0
  147. package/dist/plugin/vitek-preview.d.ts.map +1 -0
  148. package/dist/plugin/vitek-preview.js +107 -0
  149. package/dist/plugin/vitek-resolve.d.ts +7 -0
  150. package/dist/plugin/vitek-resolve.d.ts.map +1 -0
  151. package/dist/plugin/vitek-resolve.js +25 -0
  152. package/dist/plugin/vitek-transform.d.ts +7 -0
  153. package/dist/plugin/vitek-transform.d.ts.map +1 -0
  154. package/dist/plugin/vitek-transform.js +55 -0
  155. package/dist/plugin/vitek.d.ts +10 -0
  156. package/dist/plugin/vitek.d.ts.map +1 -0
  157. package/dist/plugin/vitek.js +27 -0
  158. package/dist/plugin.d.ts +3 -32
  159. package/dist/plugin.d.ts.map +1 -1
  160. package/dist/plugin.js +2 -274
  161. package/dist/plugin.test.js +99 -29
  162. package/dist/shared/response-helpers.d.ts +21 -0
  163. package/dist/shared/response-helpers.d.ts.map +1 -1
  164. package/dist/shared/response-helpers.js +41 -0
  165. package/dist/shared/response-helpers.test.js +54 -1
  166. package/package.json +19 -4
@@ -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,50 +140,52 @@ 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');
161
179
  });
162
- it('exposes resolveId and transform', () => {
163
- expect(plugin.resolveId).toBeDefined();
164
- expect(plugin.transform).toBeDefined();
180
+ it('exposes resolveId and transform on respective plugins', () => {
181
+ expect(resolvePlugin.resolveId).toBeDefined();
182
+ expect(transformPlugin.transform).toBeDefined();
165
183
  });
166
184
  });
167
185
  describe('buildStart', () => {
168
186
  it('generates api.services.ts and api.types.ts when buildStart runs', async () => {
169
187
  fs.writeFileSync(path.join(rootDir, 'tsconfig.json'), '{}', 'utf-8');
170
- const buildStart = plugin.buildStart;
188
+ const buildStart = buildPlugin.buildStart;
171
189
  if (!buildStart) {
172
190
  expect.fail('Plugin does not have buildStart hook');
173
191
  }
@@ -179,5 +197,57 @@ describe('vitek plugin resolveId and transform', () => {
179
197
  expect(fs.readFileSync(servicesPath, 'utf-8')).toContain('getHealth');
180
198
  expect(fs.readFileSync(typesPath, 'utf-8')).toContain('VitekParams');
181
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);
219
+ });
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);
182
252
  });
183
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
+ }
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { json, ok, created, noContent, badRequest, unauthorized, forbidden, notFound, conflict, unprocessableEntity, tooManyRequests, internalServerError, redirect, } from './response-helpers.js';
2
+ import { json, ok, created, noContent, badRequest, unauthorized, forbidden, notFound, conflict, unprocessableEntity, tooManyRequests, internalServerError, redirect, text, html, cacheControl, noStore, } from './response-helpers.js';
3
3
  describe('json', () => {
4
4
  it('should create JSON response with default status 200', () => {
5
5
  const response = json({ message: 'Hello' });
@@ -165,3 +165,56 @@ describe('redirect', () => {
165
165
  expect(response.status).toBe(308);
166
166
  });
167
167
  });
168
+ describe('text', () => {
169
+ it('creates 200 response with text/plain', () => {
170
+ const response = text('Hello world');
171
+ expect(response.status).toBe(200);
172
+ expect(response.headers).toEqual({ 'Content-Type': 'text/plain; charset=utf-8' });
173
+ expect(response.body).toBe('Hello world');
174
+ });
175
+ it('accepts custom status', () => {
176
+ const response = text('Error', 503);
177
+ expect(response.status).toBe(503);
178
+ expect(response.body).toBe('Error');
179
+ });
180
+ });
181
+ describe('html', () => {
182
+ it('creates 200 response with text/html', () => {
183
+ const response = html('<p>Hi</p>');
184
+ expect(response.status).toBe(200);
185
+ expect(response.headers).toEqual({ 'Content-Type': 'text/html; charset=utf-8' });
186
+ expect(response.body).toBe('<p>Hi</p>');
187
+ });
188
+ it('accepts custom status', () => {
189
+ const response = html('<h1>Not Found</h1>', 404);
190
+ expect(response.status).toBe(404);
191
+ });
192
+ });
193
+ describe('cacheControl', () => {
194
+ it('returns Cache-Control max-age only', () => {
195
+ const headers = cacheControl(60);
196
+ expect(headers).toEqual({ 'Cache-Control': 'max-age=60' });
197
+ });
198
+ it('includes stale-while-revalidate when provided', () => {
199
+ const headers = cacheControl(60, { staleWhileRevalidate: 120 });
200
+ expect(headers['Cache-Control']).toContain('max-age=60');
201
+ expect(headers['Cache-Control']).toContain('stale-while-revalidate=120');
202
+ });
203
+ it('includes private when set', () => {
204
+ const headers = cacheControl(300, { private: true });
205
+ expect(headers['Cache-Control']).toContain('max-age=300');
206
+ expect(headers['Cache-Control']).toContain('private');
207
+ });
208
+ it('combines all options', () => {
209
+ const headers = cacheControl(60, { staleWhileRevalidate: 120, private: true });
210
+ expect(headers['Cache-Control']).toContain('max-age=60');
211
+ expect(headers['Cache-Control']).toContain('stale-while-revalidate=120');
212
+ expect(headers['Cache-Control']).toContain('private');
213
+ });
214
+ });
215
+ describe('noStore', () => {
216
+ it('returns Cache-Control no-store', () => {
217
+ const headers = noStore();
218
+ expect(headers).toEqual({ 'Cache-Control': 'no-store' });
219
+ });
220
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vitek-plugin",
3
- "version": "0.1.2-beta.6",
3
+ "version": "0.2.0-beta",
4
4
  "description": "Vite plugin for file-based HTTP API generation",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -32,27 +32,34 @@
32
32
  "url": "https://github.com/martinsbicudo/vitek-plugin/issues"
33
33
  },
34
34
  "peerDependencies": {
35
- "vite": "^5.0.0"
35
+ "vite": "^5.0.0 || ^6.0.0"
36
36
  },
37
37
  "dependencies": {
38
+ "@modelcontextprotocol/sdk": "^1.0.0",
38
39
  "connect": "^3.7.0",
40
+ "zod": "^3.23.0",
39
41
  "esbuild": "^0.24.0",
42
+ "magic-string": "^0.30.0",
40
43
  "serve-static": "^1.16.0",
41
44
  "ws": "^8.18.0"
42
45
  },
43
46
  "devDependencies": {
47
+ "@commitlint/cli": "^20.4.2",
48
+ "@commitlint/config-conventional": "^20.4.2",
44
49
  "@types/connect": "^3.4.0",
45
50
  "@types/node": "^20.0.0",
46
51
  "@types/serve-static": "^1.15.0",
47
52
  "@types/ws": "^8.18.1",
48
53
  "@vitest/coverage-v8": "^4.0.18",
49
54
  "concurrently": "^9.0.0",
55
+ "husky": "^9.1.7",
50
56
  "typescript": "^5.0.0",
51
- "vite": "^5.0.0",
57
+ "vite": "^5.0.0 || ^6.0.0",
52
58
  "vitepress": "^1.0.0",
53
59
  "vitest": "^4.0.18"
54
60
  },
55
61
  "bin": {
62
+ "vitek": "./dist/cli/cli.js",
56
63
  "vitek-serve": "./dist/cli/serve.js"
57
64
  },
58
65
  "scripts": {
@@ -64,6 +71,14 @@
64
71
  "docs:preview": "vitepress preview docs",
65
72
  "test": "vitest run",
66
73
  "test:watch": "vitest",
67
- "test:coverage": "vitest run --coverage"
74
+ "test:coverage": "vitest run --coverage",
75
+ "test:e2e": "node scripts/e2e.mjs",
76
+ "test:e2e:socket": "node scripts/e2e-socket.mjs",
77
+ "bench": "node scripts/bench.mjs",
78
+ "example:bench": "node scripts/bench.mjs --with-example",
79
+ "examples:build": "bash examples/build-all.sh",
80
+ "examples:test": "bash examples/run-all-tests.sh",
81
+ "examples:build-and-test": "bash examples/build-and-test.sh",
82
+ "check": "pnpm i && pnpm run build && pnpm test && pnpm run examples:build-and-test && pnpm run test:e2e && pnpm run test:e2e:socket && pnpm run example:bench"
68
83
  }
69
84
  }