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