vitek-plugin 0.1.2-beta.6 → 0.2.1-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 +26 -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 +80 -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 +32 -15
@@ -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":""}
@@ -0,0 +1,50 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { buildApiBundle, getApiBundleFilename } from './build-api-bundle.js';
5
+ describe('build-api-bundle', () => {
6
+ let rootDir;
7
+ let apiDir;
8
+ let outDir;
9
+ beforeEach(() => {
10
+ rootDir = fs.mkdtempSync(path.join(process.cwd(), 'build-api-test-'));
11
+ apiDir = path.join(rootDir, 'src', 'api');
12
+ outDir = path.join(rootDir, 'dist');
13
+ fs.mkdirSync(apiDir, { recursive: true });
14
+ fs.mkdirSync(outDir, { recursive: true });
15
+ fs.writeFileSync(path.join(apiDir, 'health.get.ts'), 'export default function handler() { return { ok: true }; }\n', 'utf-8');
16
+ });
17
+ afterEach(() => {
18
+ try {
19
+ fs.rmSync(rootDir, { recursive: true });
20
+ }
21
+ catch {
22
+ // ignore
23
+ }
24
+ });
25
+ it('getApiBundleFilename returns vitek-api.mjs', () => {
26
+ expect(getApiBundleFilename()).toBe('vitek-api.mjs');
27
+ });
28
+ it('returns null when apiDir does not exist', async () => {
29
+ const result = await buildApiBundle({
30
+ root: rootDir,
31
+ apiDir: path.join(rootDir, 'nonexistent'),
32
+ outDir,
33
+ });
34
+ expect(result).toBeNull();
35
+ });
36
+ it('returns null when apiDir has no routes', async () => {
37
+ fs.unlinkSync(path.join(apiDir, 'health.get.ts'));
38
+ const result = await buildApiBundle({ root: rootDir, apiDir, outDir });
39
+ expect(result).toBeNull();
40
+ });
41
+ it('builds vitek-api.mjs when apiDir has routes', async () => {
42
+ const result = await buildApiBundle({ root: rootDir, apiDir, outDir });
43
+ expect(result).toBe(path.join(outDir, 'vitek-api.mjs'));
44
+ expect(fs.existsSync(result)).toBe(true);
45
+ const content = fs.readFileSync(result, 'utf-8');
46
+ expect(content).toContain('routes');
47
+ expect(content).toContain('middlewares');
48
+ expect(content).toContain('export');
49
+ });
50
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=build-sockets-bundle.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-sockets-bundle.test.d.ts","sourceRoot":"","sources":["../../src/build/build-sockets-bundle.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,49 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { buildSocketsBundle, getSocketsBundleFilename } from './build-sockets-bundle.js';
5
+ describe('build-sockets-bundle', () => {
6
+ let rootDir;
7
+ let apiDir;
8
+ let outDir;
9
+ beforeEach(() => {
10
+ rootDir = fs.mkdtempSync(path.join(process.cwd(), 'build-sockets-test-'));
11
+ apiDir = path.join(rootDir, 'src', 'api');
12
+ outDir = path.join(rootDir, 'dist');
13
+ fs.mkdirSync(apiDir, { recursive: true });
14
+ fs.mkdirSync(outDir, { recursive: true });
15
+ fs.writeFileSync(path.join(apiDir, 'chat.socket.ts'), 'export default function handler() {}\n', 'utf-8');
16
+ });
17
+ afterEach(() => {
18
+ try {
19
+ fs.rmSync(rootDir, { recursive: true });
20
+ }
21
+ catch {
22
+ // ignore
23
+ }
24
+ });
25
+ it('getSocketsBundleFilename returns vitek-sockets.mjs', () => {
26
+ expect(getSocketsBundleFilename()).toBe('vitek-sockets.mjs');
27
+ });
28
+ it('returns null when apiDir does not exist', async () => {
29
+ const result = await buildSocketsBundle({
30
+ root: rootDir,
31
+ apiDir: path.join(rootDir, 'nonexistent'),
32
+ outDir,
33
+ });
34
+ expect(result).toBeNull();
35
+ });
36
+ it('returns null when apiDir has no socket files', async () => {
37
+ fs.unlinkSync(path.join(apiDir, 'chat.socket.ts'));
38
+ const result = await buildSocketsBundle({ root: rootDir, apiDir, outDir });
39
+ expect(result).toBeNull();
40
+ });
41
+ it('builds vitek-sockets.mjs when apiDir has socket files', async () => {
42
+ const result = await buildSocketsBundle({ root: rootDir, apiDir, outDir });
43
+ expect(result).toBe(path.join(outDir, 'vitek-sockets.mjs'));
44
+ expect(fs.existsSync(result)).toBe(true);
45
+ const content = fs.readFileSync(result, 'utf-8');
46
+ expect(content).toContain('sockets');
47
+ expect(content).toContain('export');
48
+ });
49
+ });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Main CLI entry: vitek [command]
3
+ * Commands: init, mcp, serve (default)
4
+ * Usage: vitek init [--force] | vitek mcp | vitek [serve] [options]
5
+ */
6
+ declare const cmd: string;
7
+ declare function run(): Promise<void>;
8
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,QAAA,MAAU,GAAG,QAAgB,CAAC;AAE9B,iBAAe,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAalC"}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ /**
3
+ * Main CLI entry: vitek [command]
4
+ * Commands: init, mcp, serve (default)
5
+ * Usage: vitek init [--force] | vitek mcp | vitek [serve] [options]
6
+ */
7
+ const [, , cmd] = process.argv;
8
+ async function run() {
9
+ if (cmd === 'init') {
10
+ const { runInit } = await import('./init.js');
11
+ await runInit();
12
+ return;
13
+ }
14
+ if (cmd === 'mcp') {
15
+ const { runMcpProject } = await import('./mcp-project.js');
16
+ await runMcpProject();
17
+ return;
18
+ }
19
+ const { main } = await import('./serve.js');
20
+ await main();
21
+ }
22
+ run().catch((err) => {
23
+ console.error('[vitek]', err);
24
+ process.exit(1);
25
+ });
@@ -0,0 +1,6 @@
1
+ /** Fixture for serve.test.ts: config that exports beforeApiRequest, onError, onServerStart, onServerShutdown */
2
+ export function beforeApiRequest(_ctx: any, next: any): void;
3
+ export function onError(_err: any, _req: any, res: any): void;
4
+ export function onServerStart(ctx: any): void;
5
+ export function onServerShutdown(): void;
6
+ //# sourceMappingURL=vitek.config.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitek.config.d.mts","sourceRoot":"","sources":["../../../../src/cli/fixtures/serve-config/vitek.config.mjs"],"names":[],"mappings":"AAAA,gHAAgH;AAChH,6DAEC;AAED,8DAGC;AAED,8CAKC;AAED,yCAIC"}
@@ -0,0 +1,19 @@
1
+ /** Fixture for serve.test.ts: config that exports beforeApiRequest, onError, onServerStart, onServerShutdown */
2
+ export function beforeApiRequest(_ctx, next) {
3
+ next();
4
+ }
5
+ export function onError(_err, _req, res) {
6
+ res.statusCode = 503;
7
+ res.end('Unavailable');
8
+ }
9
+ export function onServerStart(ctx) {
10
+ if (typeof globalThis !== 'undefined') {
11
+ globalThis.__vitekOnServerStartCtx = ctx;
12
+ globalThis.__vitekOnServerStartCalled = true;
13
+ }
14
+ }
15
+ export function onServerShutdown() {
16
+ if (typeof globalThis !== 'undefined') {
17
+ globalThis.__vitekOnServerShutdownCalled = true;
18
+ }
19
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * vitek init: scaffold src/api and add vitek to vite.config
3
+ * Usage: vitek init [--force]
4
+ * Idempotent: does not overwrite existing files unless --force.
5
+ */
6
+ export declare function parseInitArgs(): {
7
+ force: boolean;
8
+ };
9
+ /**
10
+ * Injects vitek import and plugin into config content if not already present.
11
+ * Returns new content or null if no change needed.
12
+ */
13
+ export declare function injectVitekIntoConfig(content: string): string | null;
14
+ export declare function runInit(): Promise<void>;
15
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAkBH,wBAAgB,aAAa,IAAI;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CAOlD;AAsBD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAmBpE;AAED,wBAAsB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CA+B7C"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * vitek init: scaffold src/api and add vitek to vite.config
3
+ * Usage: vitek init [--force]
4
+ * Idempotent: does not overwrite existing files unless --force.
5
+ */
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ const DEFAULT_API_DIR = 'src/api';
9
+ const HEALTH_ROUTE_JS = `/**
10
+ * Health check endpoint
11
+ * GET /api/health
12
+ */
13
+
14
+ export default function handler() {
15
+ return { ok: true, timestamp: new Date().toISOString() };
16
+ }
17
+ `;
18
+ const VITE_CONFIG_NAMES = ['vite.config.js', 'vite.config.ts', 'vite.config.mjs', 'vite.config.cjs'];
19
+ export function parseInitArgs() {
20
+ const argv = process.argv.slice(2);
21
+ let force = false;
22
+ for (const arg of argv) {
23
+ if (arg === '--force' || arg === '-f')
24
+ force = true;
25
+ }
26
+ return { force };
27
+ }
28
+ /**
29
+ * Ensures directory exists. Creates it and parents if needed.
30
+ */
31
+ function ensureDir(dir) {
32
+ if (!fs.existsSync(dir)) {
33
+ fs.mkdirSync(dir, { recursive: true });
34
+ }
35
+ }
36
+ /**
37
+ * Finds the first existing vite config file in cwd.
38
+ */
39
+ function findViteConfig(cwd) {
40
+ for (const name of VITE_CONFIG_NAMES) {
41
+ const p = path.join(cwd, name);
42
+ if (fs.existsSync(p) && fs.statSync(p).isFile())
43
+ return p;
44
+ }
45
+ return null;
46
+ }
47
+ /**
48
+ * Injects vitek import and plugin into config content if not already present.
49
+ * Returns new content or null if no change needed.
50
+ */
51
+ export function injectVitekIntoConfig(content) {
52
+ const hasVitek = /vitek-plugin|vitek\s*\(/.test(content);
53
+ if (hasVitek)
54
+ return null;
55
+ let out = content;
56
+ // Add import after first line (so it's at the top with other imports)
57
+ if (!out.includes('vitek-plugin')) {
58
+ const firstNewline = out.indexOf('\n');
59
+ const insertIdx = firstNewline === -1 ? out.length : firstNewline + 1;
60
+ out = out.slice(0, insertIdx) + "import { vitek } from 'vitek-plugin';\n" + out.slice(insertIdx);
61
+ }
62
+ // Add vitek() to plugins array: find plugins: [ and insert vitek(), as first element
63
+ const pluginsMatch = out.match(/plugins:\s*\[/);
64
+ if (!pluginsMatch)
65
+ return null;
66
+ const insertPluginsIdx = pluginsMatch.index + pluginsMatch[0].length;
67
+ out = out.slice(0, insertPluginsIdx) + 'vitek(), ' + out.slice(insertPluginsIdx);
68
+ return out;
69
+ }
70
+ export async function runInit() {
71
+ const { force } = parseInitArgs();
72
+ const cwd = process.cwd();
73
+ const apiDir = path.join(cwd, DEFAULT_API_DIR);
74
+ const healthPath = path.join(apiDir, 'health.get.ts');
75
+ ensureDir(apiDir);
76
+ if (!fs.existsSync(healthPath) || force) {
77
+ fs.writeFileSync(healthPath, HEALTH_ROUTE_JS, 'utf-8');
78
+ console.log(`[vitek init] Created ${DEFAULT_API_DIR}/health.get.ts`);
79
+ }
80
+ else {
81
+ console.log(`[vitek init] ${DEFAULT_API_DIR}/health.get.ts already exists (use --force to overwrite)`);
82
+ }
83
+ const configPath = findViteConfig(cwd);
84
+ if (configPath) {
85
+ const content = fs.readFileSync(configPath, 'utf-8');
86
+ const newContent = injectVitekIntoConfig(content);
87
+ if (newContent) {
88
+ fs.writeFileSync(configPath, newContent, 'utf-8');
89
+ console.log(`[vitek init] Added vitek to ${path.basename(configPath)}`);
90
+ }
91
+ else {
92
+ console.log(`[vitek init] ${path.basename(configPath)} already has vitek`);
93
+ }
94
+ }
95
+ else {
96
+ console.log('[vitek init] No vite.config.js/ts/mjs/cjs found; add vitek() to your Vite config manually. See https://martinsbicudo.github.io/vitek-plugin/guide/getting-started');
97
+ }
98
+ console.log('[vitek init] Tip: expose your API to AI assistants with `vitek mcp` — https://martinsbicudo.github.io/vitek-plugin/guide/mcp-project');
99
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=init.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.test.d.ts","sourceRoot":"","sources":["../../src/cli/init.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,117 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
5
+ import { runInit, parseInitArgs, injectVitekIntoConfig } from './init.js';
6
+ describe('parseInitArgs', () => {
7
+ it('returns force false when no args', () => {
8
+ process.argv = ['node', 'vitek', 'init'];
9
+ expect(parseInitArgs().force).toBe(false);
10
+ });
11
+ it('returns force true when --force', () => {
12
+ process.argv = ['node', 'vitek', 'init', '--force'];
13
+ expect(parseInitArgs().force).toBe(true);
14
+ });
15
+ it('returns force true when -f', () => {
16
+ process.argv = ['node', 'vitek', 'init', '-f'];
17
+ expect(parseInitArgs().force).toBe(true);
18
+ });
19
+ });
20
+ describe('injectVitekIntoConfig', () => {
21
+ it('returns null when vitek is already present', () => {
22
+ const content = "import { vitek } from 'vitek-plugin';\nexport default defineConfig({ plugins: [vitek()] });";
23
+ expect(injectVitekIntoConfig(content)).toBeNull();
24
+ });
25
+ it('adds import and vitek() to plugins when missing', () => {
26
+ const content = `import { defineConfig } from 'vite';
27
+
28
+ export default defineConfig({
29
+ plugins: [],
30
+ });
31
+ `;
32
+ const result = injectVitekIntoConfig(content);
33
+ expect(result).not.toBeNull();
34
+ expect(result).toContain("import { vitek } from 'vitek-plugin'");
35
+ expect(result).toContain('vitek(), ');
36
+ expect(result).toMatch(/plugins:\s*\[\s*vitek\(\)/);
37
+ });
38
+ it('returns null when plugins array is missing', () => {
39
+ const content = 'export default { root: "." }';
40
+ expect(injectVitekIntoConfig(content)).toBeNull();
41
+ });
42
+ });
43
+ describe('runInit', () => {
44
+ let tmpDir;
45
+ const originalCwd = process.cwd();
46
+ beforeEach(() => {
47
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vitek-init-test-'));
48
+ });
49
+ afterEach(() => {
50
+ process.chdir(originalCwd);
51
+ try {
52
+ fs.rmSync(tmpDir, { recursive: true });
53
+ }
54
+ catch {
55
+ // ignore
56
+ }
57
+ });
58
+ it('creates src/api and health.get.ts', async () => {
59
+ process.chdir(tmpDir);
60
+ await runInit();
61
+ const healthPath = path.join(tmpDir, 'src', 'api', 'health.get.ts');
62
+ expect(fs.existsSync(healthPath)).toBe(true);
63
+ const content = fs.readFileSync(healthPath, 'utf-8');
64
+ expect(content).toContain('export default function handler()');
65
+ expect(content).toContain('ok: true');
66
+ });
67
+ it('does not overwrite health.get.ts on second run without --force', async () => {
68
+ process.chdir(tmpDir);
69
+ process.argv = ['node', 'vitek', 'init'];
70
+ await runInit();
71
+ const healthPath = path.join(tmpDir, 'src', 'api', 'health.get.ts');
72
+ fs.writeFileSync(healthPath, '// custom content', 'utf-8');
73
+ process.argv = ['node', 'vitek', 'init'];
74
+ await runInit();
75
+ expect(fs.readFileSync(healthPath, 'utf-8')).toBe('// custom content');
76
+ });
77
+ it('overwrites health.get.ts when --force', async () => {
78
+ process.chdir(tmpDir);
79
+ await runInit();
80
+ const healthPath = path.join(tmpDir, 'src', 'api', 'health.get.ts');
81
+ fs.writeFileSync(healthPath, '// custom', 'utf-8');
82
+ process.argv = ['node', 'vitek', 'init', '--force'];
83
+ await runInit();
84
+ expect(fs.readFileSync(healthPath, 'utf-8')).toContain('export default function handler()');
85
+ });
86
+ it('adds vitek to existing vite.config.js', async () => {
87
+ process.chdir(tmpDir);
88
+ fs.mkdirSync(path.join(tmpDir, 'src', 'api'), { recursive: true });
89
+ const configPath = path.join(tmpDir, 'vite.config.js');
90
+ fs.writeFileSync(configPath, `import { defineConfig } from 'vite';
91
+
92
+ export default defineConfig({
93
+ plugins: [],
94
+ });
95
+ `, 'utf-8');
96
+ await runInit();
97
+ const content = fs.readFileSync(configPath, 'utf-8');
98
+ expect(content).toContain("import { vitek } from 'vitek-plugin'");
99
+ expect(content).toContain('vitek(), ');
100
+ });
101
+ it('does not duplicate vitek in config on second run', async () => {
102
+ process.chdir(tmpDir);
103
+ fs.mkdirSync(path.join(tmpDir, 'src', 'api'), { recursive: true });
104
+ const configPath = path.join(tmpDir, 'vite.config.js');
105
+ fs.writeFileSync(configPath, `import { defineConfig } from 'vite';
106
+ import { vitek } from 'vitek-plugin';
107
+
108
+ export default defineConfig({
109
+ plugins: [vitek()],
110
+ });
111
+ `, 'utf-8');
112
+ await runInit();
113
+ const content = fs.readFileSync(configPath, 'utf-8');
114
+ const vitekCount = (content.match(/vitek/g) || []).length;
115
+ expect(vitekCount).toBeLessThanOrEqual(3); // import + two in plugins
116
+ });
117
+ });