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
|
@@ -9,32 +9,57 @@ import { ROUTE_FILE_PATTERN, MIDDLEWARE_FILE_PATTERN } from '../../shared/consta
|
|
|
9
9
|
* Creates a watcher for the API directory using Node.js fs.watch
|
|
10
10
|
* Returns a function to stop watching
|
|
11
11
|
*/
|
|
12
|
-
export function watchApiDirectory(apiDir, callback) {
|
|
12
|
+
export function watchApiDirectory(apiDir, callback, options = {}) {
|
|
13
13
|
if (!fs.existsSync(apiDir)) {
|
|
14
14
|
return { close: () => { } };
|
|
15
15
|
}
|
|
16
|
+
const debounceMs = options.debounceMs ?? 0;
|
|
17
|
+
let pending = [];
|
|
18
|
+
let timer = null;
|
|
19
|
+
const flush = () => {
|
|
20
|
+
if (timer != null) {
|
|
21
|
+
clearTimeout(timer);
|
|
22
|
+
timer = null;
|
|
23
|
+
}
|
|
24
|
+
const toEmit = pending;
|
|
25
|
+
pending = [];
|
|
26
|
+
for (const { event, filePath } of toEmit) {
|
|
27
|
+
callback(event, filePath);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const schedule = (event, filePath) => {
|
|
31
|
+
pending.push({ event, filePath });
|
|
32
|
+
if (debounceMs <= 0) {
|
|
33
|
+
flush();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (timer != null)
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
timer = setTimeout(flush, debounceMs);
|
|
39
|
+
};
|
|
16
40
|
const watcher = fs.watch(apiDir, { recursive: true }, (eventType, filename) => {
|
|
17
41
|
if (!filename)
|
|
18
42
|
return;
|
|
19
43
|
const filePath = path.join(apiDir, filename);
|
|
20
|
-
// Ignore if it's not a route file or relevant middleware
|
|
21
44
|
const isRouteFile = ROUTE_FILE_PATTERN.test(filename);
|
|
22
45
|
const isMiddlewareFile = MIDDLEWARE_FILE_PATTERN.test(filename);
|
|
23
46
|
if (!isRouteFile && !isMiddlewareFile) {
|
|
24
47
|
return;
|
|
25
48
|
}
|
|
26
|
-
// Normalize event type
|
|
27
49
|
let normalizedEvent;
|
|
28
50
|
if (eventType === 'rename') {
|
|
29
|
-
// rename can be add or unlink, check if it exists
|
|
30
51
|
normalizedEvent = fs.existsSync(filePath) ? 'add' : 'unlink';
|
|
31
52
|
}
|
|
32
53
|
else {
|
|
33
54
|
normalizedEvent = eventType;
|
|
34
55
|
}
|
|
35
|
-
|
|
56
|
+
schedule(normalizedEvent, filePath);
|
|
36
57
|
});
|
|
37
58
|
return {
|
|
38
|
-
close: () =>
|
|
59
|
+
close: () => {
|
|
60
|
+
if (timer != null)
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
watcher.close();
|
|
63
|
+
},
|
|
39
64
|
};
|
|
40
65
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch-api-dir.test.d.ts","sourceRoot":"","sources":["../../../src/core/file-system/watch-api-dir.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { watchApiDirectory } from './watch-api-dir.js';
|
|
5
|
+
describe('watch-api-dir', () => {
|
|
6
|
+
let tmpDir;
|
|
7
|
+
let apiDir;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
tmpDir = fs.mkdtempSync(path.join(process.cwd(), 'watch-api-'));
|
|
10
|
+
apiDir = path.join(tmpDir, 'api');
|
|
11
|
+
fs.mkdirSync(apiDir, { recursive: true });
|
|
12
|
+
});
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
try {
|
|
15
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// ignore
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
it('returns watcher with no-op close when apiDir does not exist', () => {
|
|
22
|
+
const watcher = watchApiDirectory(path.join(tmpDir, 'nonexistent'), () => { });
|
|
23
|
+
expect(watcher).toBeDefined();
|
|
24
|
+
expect(typeof watcher.close).toBe('function');
|
|
25
|
+
watcher.close();
|
|
26
|
+
});
|
|
27
|
+
it('returns watcher with close when apiDir exists', () => {
|
|
28
|
+
const watcher = watchApiDirectory(apiDir, () => { });
|
|
29
|
+
expect(watcher).toBeDefined();
|
|
30
|
+
expect(typeof watcher.close).toBe('function');
|
|
31
|
+
watcher.close();
|
|
32
|
+
});
|
|
33
|
+
it('accepts debounceMs option', () => {
|
|
34
|
+
const watcher = watchApiDirectory(apiDir, () => { }, { debounceMs: 100 });
|
|
35
|
+
expect(watcher).toBeDefined();
|
|
36
|
+
watcher.close();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ParsedRoute } from '../routing/route-parser.js';
|
|
2
|
+
import type { ParsedSocket } from '../routing/socket-parser.js';
|
|
3
|
+
import type { RouteSchema } from '../types/schema.js';
|
|
4
|
+
import { type OpenApiOptions } from '../openapi/generate.js';
|
|
5
|
+
export declare function parsedRoutesToSchema(parsedRoutes: ParsedRoute[]): RouteSchema[];
|
|
6
|
+
export interface RunFileGenerationOptions {
|
|
7
|
+
root: string;
|
|
8
|
+
schema: RouteSchema[];
|
|
9
|
+
sockets: ParsedSocket[];
|
|
10
|
+
apiBasePath?: string;
|
|
11
|
+
socketBasePath?: string;
|
|
12
|
+
openApi?: OpenApiOptions | boolean;
|
|
13
|
+
serverPort?: number;
|
|
14
|
+
logger?: {
|
|
15
|
+
typesGenerated?: (path: string) => void;
|
|
16
|
+
servicesGenerated?: (path: string) => void;
|
|
17
|
+
info?: (message: string) => void;
|
|
18
|
+
warn?: (message: string) => void;
|
|
19
|
+
};
|
|
20
|
+
/** Callback when generation fails (e.g. OpenAPI). Does not run for types/services write errors. */
|
|
21
|
+
onGenerationError?: (error: Error) => void;
|
|
22
|
+
}
|
|
23
|
+
export declare function runFileGeneration(options: RunFileGenerationOptions): Promise<void>;
|
|
24
|
+
//# sourceMappingURL=run-file-generation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-file-generation.d.ts","sourceRoot":"","sources":["../../../src/core/generation/run-file-generation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAKtD,OAAO,EAA4C,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAgBvG,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAS/E;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE;QACP,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;QACxC,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;QAC3C,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;QACjC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;KAClC,CAAC;IACF,mGAAmG;IACnG,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAC5C;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuFxF"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import { extractBodyTypeFromFile, extractQueryTypeFromFile } from '../file-system/extract-type-from-file.js';
|
|
4
|
+
import { generateTypesFile, generateServicesFile } from '../types/generate.js';
|
|
5
|
+
import { generateSocketTypesFile } from '../types/generate-socket-types.js';
|
|
6
|
+
import { generateSocketServicesFile } from '../types/generate-socket-services.js';
|
|
7
|
+
import { generateOpenApiFile, generateApiDocsHtml } from '../openapi/generate.js';
|
|
8
|
+
import { generateAsyncApiFile } from '../asyncapi/generate.js';
|
|
9
|
+
import { socketsToSchema } from '../types/socket-schema.js';
|
|
10
|
+
import { API_BASE_PATH, GENERATED_TYPES_FILE, GENERATED_SERVICES_FILE, GENERATED_SOCKET_TYPES_FILE, GENERATED_SOCKET_SERVICES_FILE, } from '../../shared/constants.js';
|
|
11
|
+
function isTypeScriptProject(root) {
|
|
12
|
+
const tsconfigPath = path.join(root, 'tsconfig.json');
|
|
13
|
+
return fs.existsSync(tsconfigPath);
|
|
14
|
+
}
|
|
15
|
+
export function parsedRoutesToSchema(parsedRoutes) {
|
|
16
|
+
return parsedRoutes.map((p) => ({
|
|
17
|
+
pattern: p.pattern,
|
|
18
|
+
method: p.method,
|
|
19
|
+
params: p.params,
|
|
20
|
+
file: p.file,
|
|
21
|
+
bodyType: extractBodyTypeFromFile(p.file),
|
|
22
|
+
queryType: extractQueryTypeFromFile(p.file),
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
export async function runFileGeneration(options) {
|
|
26
|
+
const { root, schema, sockets, apiBasePath = API_BASE_PATH, socketBasePath = `${apiBasePath}/ws`, openApi, serverPort = 5173, logger, } = options;
|
|
27
|
+
const logTypes = logger?.typesGenerated ?? (() => { });
|
|
28
|
+
const logServices = logger?.servicesGenerated ?? (() => { });
|
|
29
|
+
const logInfo = logger?.info ?? (() => { });
|
|
30
|
+
const logWarn = logger?.warn ?? (() => { });
|
|
31
|
+
const onGenerationError = options.onGenerationError;
|
|
32
|
+
const isTypeScript = isTypeScriptProject(root);
|
|
33
|
+
const srcDir = path.join(root, 'src');
|
|
34
|
+
if (schema.length > 0) {
|
|
35
|
+
if (isTypeScript) {
|
|
36
|
+
const typesPath = path.join(srcDir, GENERATED_TYPES_FILE);
|
|
37
|
+
await generateTypesFile(typesPath, schema, apiBasePath);
|
|
38
|
+
logTypes(`./${path.relative(root, typesPath).replace(/\\/g, '/')}`);
|
|
39
|
+
}
|
|
40
|
+
const servicesFileName = isTypeScript ? GENERATED_SERVICES_FILE : 'api.services.js';
|
|
41
|
+
const servicesPath = path.join(srcDir, servicesFileName);
|
|
42
|
+
await generateServicesFile(servicesPath, schema, apiBasePath, isTypeScript);
|
|
43
|
+
logServices(`./${path.relative(root, servicesPath).replace(/\\/g, '/')}`);
|
|
44
|
+
}
|
|
45
|
+
if (sockets.length > 0) {
|
|
46
|
+
const socketSchema = socketsToSchema(sockets);
|
|
47
|
+
if (isTypeScript) {
|
|
48
|
+
const socketTypesPath = path.join(srcDir, GENERATED_SOCKET_TYPES_FILE);
|
|
49
|
+
await generateSocketTypesFile(socketTypesPath, socketSchema, socketBasePath);
|
|
50
|
+
logTypes(`./${path.relative(root, socketTypesPath).replace(/\\/g, '/')}`);
|
|
51
|
+
}
|
|
52
|
+
const socketServicesFileName = isTypeScript ? GENERATED_SOCKET_SERVICES_FILE : 'socket.services.js';
|
|
53
|
+
const socketServicesPath = path.join(srcDir, socketServicesFileName);
|
|
54
|
+
await generateSocketServicesFile(socketServicesPath, socketSchema, socketBasePath, isTypeScript);
|
|
55
|
+
logServices(`./${path.relative(root, socketServicesPath).replace(/\\/g, '/')}`);
|
|
56
|
+
}
|
|
57
|
+
if (openApi) {
|
|
58
|
+
try {
|
|
59
|
+
const openApiOptions = typeof openApi === 'boolean'
|
|
60
|
+
? { apiBasePath }
|
|
61
|
+
: { ...openApi, apiBasePath: openApi.apiBasePath ?? apiBasePath };
|
|
62
|
+
const publicDir = path.join(root, 'public');
|
|
63
|
+
if (!fs.existsSync(publicDir)) {
|
|
64
|
+
fs.mkdirSync(publicDir, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
const openApiPath = path.join(publicDir, 'openapi.json');
|
|
67
|
+
await generateOpenApiFile(openApiPath, schema, openApiOptions);
|
|
68
|
+
logInfo(`OpenAPI spec generated: ./${path.relative(root, openApiPath).replace(/\\/g, '/')}`);
|
|
69
|
+
if (sockets.length > 0) {
|
|
70
|
+
const asyncApiPath = path.join(publicDir, 'asyncapi.json');
|
|
71
|
+
await generateAsyncApiFile(asyncApiPath, sockets, socketBasePath, {
|
|
72
|
+
serverUrl: `ws://localhost:${serverPort}`,
|
|
73
|
+
});
|
|
74
|
+
logInfo(`AsyncAPI spec generated: ./${path.relative(root, asyncApiPath).replace(/\\/g, '/')}`);
|
|
75
|
+
}
|
|
76
|
+
const apiDocsPath = path.join(publicDir, 'api-docs.html');
|
|
77
|
+
const title = openApiOptions.info?.title ?? 'Vitek API';
|
|
78
|
+
const apiDocsHtml = generateApiDocsHtml('/openapi.json', title, {
|
|
79
|
+
asyncApiJsonPath: sockets.length > 0 ? '/asyncapi.json' : undefined,
|
|
80
|
+
});
|
|
81
|
+
fs.writeFileSync(apiDocsPath, apiDocsHtml, 'utf-8');
|
|
82
|
+
logInfo(`API docs at: ./${path.relative(root, apiDocsPath).replace(/\\/g, '/')} → http://localhost:${serverPort}/api-docs.html`);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
86
|
+
logWarn(`Failed to generate OpenAPI spec: ${err.message}`);
|
|
87
|
+
onGenerationError?.(err);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-file-generation.test.d.ts","sourceRoot":"","sources":["../../../src/core/generation/run-file-generation.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { parsedRoutesToSchema, runFileGeneration } from './run-file-generation.js';
|
|
5
|
+
function parsedRoute(pattern, method, params = [], file) {
|
|
6
|
+
return {
|
|
7
|
+
method: method,
|
|
8
|
+
pattern,
|
|
9
|
+
params,
|
|
10
|
+
file: file ?? `/api/${pattern.replace(/:/g, '[id]')}.${method}.ts`,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function parsedSocket(pattern, params = []) {
|
|
14
|
+
return {
|
|
15
|
+
pattern,
|
|
16
|
+
params,
|
|
17
|
+
file: `/api/${pattern.replace(/:/g, '[id]')}.socket.ts`,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
describe('parsedRoutesToSchema', () => {
|
|
21
|
+
let tmpDir;
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
tmpDir = fs.mkdtempSync(path.join(process.cwd(), 'run-file-gen-test-'));
|
|
24
|
+
});
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
try {
|
|
27
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
//
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
it('returns schema with pattern, method, params', () => {
|
|
34
|
+
const routeFile = path.join(tmpDir, 'health.get.ts');
|
|
35
|
+
fs.writeFileSync(routeFile, 'export default function handler() {}', 'utf-8');
|
|
36
|
+
const routes = [
|
|
37
|
+
{
|
|
38
|
+
method: 'get',
|
|
39
|
+
pattern: 'health',
|
|
40
|
+
params: [],
|
|
41
|
+
file: routeFile,
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
const schema = parsedRoutesToSchema(routes);
|
|
45
|
+
expect(schema).toHaveLength(1);
|
|
46
|
+
expect(schema[0].pattern).toBe('health');
|
|
47
|
+
expect(schema[0].method).toBe('get');
|
|
48
|
+
expect(schema[0].params).toEqual([]);
|
|
49
|
+
});
|
|
50
|
+
it('extracts bodyType when route file exports Body', () => {
|
|
51
|
+
const routeFile = path.join(tmpDir, 'users.post.ts');
|
|
52
|
+
fs.writeFileSync(routeFile, `export type Body = { email: string; name?: string; };
|
|
53
|
+
export default function handler() {}`, 'utf-8');
|
|
54
|
+
const routes = [
|
|
55
|
+
{
|
|
56
|
+
method: 'post',
|
|
57
|
+
pattern: 'users',
|
|
58
|
+
params: [],
|
|
59
|
+
file: routeFile,
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
const schema = parsedRoutesToSchema(routes);
|
|
63
|
+
expect(schema[0].bodyType).toBe('{ email: string; name?: string; }');
|
|
64
|
+
});
|
|
65
|
+
it('extracts queryType when route file exports Query', () => {
|
|
66
|
+
const routeFile = path.join(tmpDir, 'posts.get.ts');
|
|
67
|
+
fs.writeFileSync(routeFile, `export type Query = { limit?: number; };
|
|
68
|
+
export default function handler() {}`, 'utf-8');
|
|
69
|
+
const routes = [
|
|
70
|
+
{
|
|
71
|
+
method: 'get',
|
|
72
|
+
pattern: 'posts',
|
|
73
|
+
params: [],
|
|
74
|
+
file: routeFile,
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
const schema = parsedRoutesToSchema(routes);
|
|
78
|
+
expect(schema[0].queryType).toBe('{ limit?: number; }');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
describe('runFileGeneration', () => {
|
|
82
|
+
let tmpDir;
|
|
83
|
+
beforeEach(() => {
|
|
84
|
+
tmpDir = fs.mkdtempSync(path.join(process.cwd(), 'run-file-gen-test-'));
|
|
85
|
+
fs.mkdirSync(path.join(tmpDir, 'src'), { recursive: true });
|
|
86
|
+
});
|
|
87
|
+
afterEach(() => {
|
|
88
|
+
try {
|
|
89
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
//
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
it('generates api.types.ts and api.services.ts for TypeScript project', async () => {
|
|
96
|
+
fs.writeFileSync(path.join(tmpDir, 'tsconfig.json'), '{}', 'utf-8');
|
|
97
|
+
const routeFile = path.join(tmpDir, 'src', 'api', 'health.get.ts');
|
|
98
|
+
fs.mkdirSync(path.dirname(routeFile), { recursive: true });
|
|
99
|
+
fs.writeFileSync(routeFile, 'export default function handler() {}', 'utf-8');
|
|
100
|
+
const schema = parsedRoutesToSchema([
|
|
101
|
+
{ method: 'get', pattern: 'health', params: [], file: routeFile },
|
|
102
|
+
]);
|
|
103
|
+
await runFileGeneration({
|
|
104
|
+
root: tmpDir,
|
|
105
|
+
schema,
|
|
106
|
+
sockets: [],
|
|
107
|
+
logger: {},
|
|
108
|
+
});
|
|
109
|
+
const typesPath = path.join(tmpDir, 'src', 'api.types.ts');
|
|
110
|
+
const servicesPath = path.join(tmpDir, 'src', 'api.services.ts');
|
|
111
|
+
expect(fs.existsSync(typesPath)).toBe(true);
|
|
112
|
+
expect(fs.existsSync(servicesPath)).toBe(true);
|
|
113
|
+
expect(fs.readFileSync(typesPath, 'utf-8')).toContain('VitekParams');
|
|
114
|
+
expect(fs.readFileSync(servicesPath, 'utf-8')).toContain('getHealth');
|
|
115
|
+
});
|
|
116
|
+
it('generates api.services.js only for JavaScript project', async () => {
|
|
117
|
+
const routeFile = path.join(tmpDir, 'src', 'api', 'health.get.js');
|
|
118
|
+
fs.mkdirSync(path.dirname(routeFile), { recursive: true });
|
|
119
|
+
fs.writeFileSync(routeFile, 'export default function handler() {}', 'utf-8');
|
|
120
|
+
const schema = parsedRoutesToSchema([
|
|
121
|
+
{ method: 'get', pattern: 'health', params: [], file: routeFile },
|
|
122
|
+
]);
|
|
123
|
+
await runFileGeneration({
|
|
124
|
+
root: tmpDir,
|
|
125
|
+
schema,
|
|
126
|
+
sockets: [],
|
|
127
|
+
logger: {},
|
|
128
|
+
});
|
|
129
|
+
expect(fs.existsSync(path.join(tmpDir, 'src', 'api.types.ts'))).toBe(false);
|
|
130
|
+
expect(fs.existsSync(path.join(tmpDir, 'src', 'api.services.js'))).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
it('generates OpenAPI files when openApi is enabled', async () => {
|
|
133
|
+
fs.writeFileSync(path.join(tmpDir, 'tsconfig.json'), '{}', 'utf-8');
|
|
134
|
+
fs.mkdirSync(path.join(tmpDir, 'public'), { recursive: true });
|
|
135
|
+
const routeFile = path.join(tmpDir, 'src', 'api', 'health.get.ts');
|
|
136
|
+
fs.mkdirSync(path.dirname(routeFile), { recursive: true });
|
|
137
|
+
fs.writeFileSync(routeFile, 'export default function handler() {}', 'utf-8');
|
|
138
|
+
const schema = parsedRoutesToSchema([
|
|
139
|
+
{ method: 'get', pattern: 'health', params: [], file: routeFile },
|
|
140
|
+
]);
|
|
141
|
+
await runFileGeneration({
|
|
142
|
+
root: tmpDir,
|
|
143
|
+
schema,
|
|
144
|
+
sockets: [],
|
|
145
|
+
openApi: true,
|
|
146
|
+
logger: {},
|
|
147
|
+
});
|
|
148
|
+
expect(fs.existsSync(path.join(tmpDir, 'public', 'openapi.json'))).toBe(true);
|
|
149
|
+
expect(fs.existsSync(path.join(tmpDir, 'public', 'api-docs.html'))).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ParsedRoute } from '../routing/route-parser.js';
|
|
2
|
+
import type { ParsedSocket } from '../routing/socket-parser.js';
|
|
3
|
+
export interface VitekManifest {
|
|
4
|
+
routes: Array<{
|
|
5
|
+
method: string;
|
|
6
|
+
pattern: string;
|
|
7
|
+
params: string[];
|
|
8
|
+
file: string;
|
|
9
|
+
}>;
|
|
10
|
+
middlewares: Array<{
|
|
11
|
+
basePattern: string;
|
|
12
|
+
path: string;
|
|
13
|
+
}>;
|
|
14
|
+
sockets: Array<{
|
|
15
|
+
pattern: string;
|
|
16
|
+
params: string[];
|
|
17
|
+
file: string;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
export declare function getManifest(root: string, apiDir: string): VitekManifest;
|
|
21
|
+
export declare function getRoutes(root: string, apiDir: string): ParsedRoute[];
|
|
22
|
+
export declare function getSockets(root: string, apiDir: string): ParsedSocket[];
|
|
23
|
+
export declare function writeManifest(root: string, apiDir: string, outDir: string): string;
|
|
24
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/core/introspection/manifest.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEhE,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,KAAK,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IACH,WAAW,EAAE,KAAK,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IACH,OAAO,EAAE,KAAK,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;CACJ;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,CAoBvE;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,EAAE,CAIrE;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,YAAY,EAAE,CAIvE;AAED,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,MAAM,CAMR"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import { scanApiDirectory } from '../file-system/scan-api-dir.js';
|
|
4
|
+
export function getManifest(root, apiDir) {
|
|
5
|
+
const fullApiDir = path.isAbsolute(apiDir) ? apiDir : path.resolve(root, apiDir);
|
|
6
|
+
const scanResult = scanApiDirectory(fullApiDir);
|
|
7
|
+
return {
|
|
8
|
+
routes: scanResult.routes.map((r) => ({
|
|
9
|
+
method: r.method,
|
|
10
|
+
pattern: r.pattern,
|
|
11
|
+
params: r.params,
|
|
12
|
+
file: path.relative(root, r.file).replace(/\\/g, '/'),
|
|
13
|
+
})),
|
|
14
|
+
middlewares: scanResult.middlewares.map((m) => ({
|
|
15
|
+
basePattern: m.basePattern,
|
|
16
|
+
path: path.relative(root, m.path).replace(/\\/g, '/'),
|
|
17
|
+
})),
|
|
18
|
+
sockets: scanResult.sockets.map((s) => ({
|
|
19
|
+
pattern: s.pattern,
|
|
20
|
+
params: s.params,
|
|
21
|
+
file: path.relative(root, s.file).replace(/\\/g, '/'),
|
|
22
|
+
})),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function getRoutes(root, apiDir) {
|
|
26
|
+
const fullApiDir = path.isAbsolute(apiDir) ? apiDir : path.resolve(root, apiDir);
|
|
27
|
+
const scanResult = scanApiDirectory(fullApiDir);
|
|
28
|
+
return scanResult.routes;
|
|
29
|
+
}
|
|
30
|
+
export function getSockets(root, apiDir) {
|
|
31
|
+
const fullApiDir = path.isAbsolute(apiDir) ? apiDir : path.resolve(root, apiDir);
|
|
32
|
+
const scanResult = scanApiDirectory(fullApiDir);
|
|
33
|
+
return scanResult.sockets;
|
|
34
|
+
}
|
|
35
|
+
export function writeManifest(root, apiDir, outDir) {
|
|
36
|
+
const manifest = getManifest(root, apiDir);
|
|
37
|
+
const manifestPath = path.join(outDir, 'vitek-manifest.json');
|
|
38
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
39
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
|
|
40
|
+
return manifestPath;
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.test.d.ts","sourceRoot":"","sources":["../../../src/core/introspection/manifest.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { getManifest, getRoutes, getSockets, writeManifest } from './manifest.js';
|
|
5
|
+
describe('manifest', () => {
|
|
6
|
+
let rootDir;
|
|
7
|
+
let apiDir;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
rootDir = fs.mkdtempSync(path.join(process.cwd(), 'vitek-manifest-test-'));
|
|
10
|
+
apiDir = path.join(rootDir, 'src', 'api');
|
|
11
|
+
fs.mkdirSync(apiDir, { recursive: true });
|
|
12
|
+
fs.writeFileSync(path.join(apiDir, 'health.get.ts'), 'export default () => ({});');
|
|
13
|
+
});
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
try {
|
|
16
|
+
fs.rmSync(rootDir, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// ignore cleanup errors
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
it('getManifest returns routes, middlewares, sockets', () => {
|
|
23
|
+
const manifest = getManifest(rootDir, 'src/api');
|
|
24
|
+
expect(manifest).toHaveProperty('routes');
|
|
25
|
+
expect(manifest).toHaveProperty('middlewares');
|
|
26
|
+
expect(manifest).toHaveProperty('sockets');
|
|
27
|
+
expect(Array.isArray(manifest.routes)).toBe(true);
|
|
28
|
+
expect(Array.isArray(manifest.middlewares)).toBe(true);
|
|
29
|
+
expect(Array.isArray(manifest.sockets)).toBe(true);
|
|
30
|
+
expect(manifest.routes.length).toBeGreaterThan(0);
|
|
31
|
+
});
|
|
32
|
+
it('getRoutes returns ParsedRoute[]', () => {
|
|
33
|
+
const routes = getRoutes(rootDir, 'src/api');
|
|
34
|
+
expect(Array.isArray(routes)).toBe(true);
|
|
35
|
+
routes.forEach((r) => {
|
|
36
|
+
expect(r).toHaveProperty('method');
|
|
37
|
+
expect(r).toHaveProperty('pattern');
|
|
38
|
+
expect(r).toHaveProperty('params');
|
|
39
|
+
expect(r).toHaveProperty('file');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
it('getSockets returns ParsedSocket[]', () => {
|
|
43
|
+
const sockets = getSockets(rootDir, 'src/api');
|
|
44
|
+
expect(Array.isArray(sockets)).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
it('getManifest with non-existent apiDir returns empty arrays', () => {
|
|
47
|
+
const manifest = getManifest(rootDir, 'non-existent-dir');
|
|
48
|
+
expect(manifest.routes).toEqual([]);
|
|
49
|
+
expect(manifest.middlewares).toEqual([]);
|
|
50
|
+
expect(manifest.sockets).toEqual([]);
|
|
51
|
+
});
|
|
52
|
+
it('writeManifest writes vitek-manifest.json', () => {
|
|
53
|
+
const outDir = path.join(rootDir, 'dist');
|
|
54
|
+
const written = writeManifest(rootDir, 'src/api', outDir);
|
|
55
|
+
expect(written).toBe(path.join(outDir, 'vitek-manifest.json'));
|
|
56
|
+
expect(fs.existsSync(written)).toBe(true);
|
|
57
|
+
const content = JSON.parse(fs.readFileSync(written, 'utf-8'));
|
|
58
|
+
expect(content).toHaveProperty('routes');
|
|
59
|
+
expect(content).toHaveProperty('middlewares');
|
|
60
|
+
expect(content).toHaveProperty('sockets');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -9,7 +9,14 @@ import type { Middleware } from '../routing/route-types.js';
|
|
|
9
9
|
export interface LoadedMiddleware {
|
|
10
10
|
middleware: Middleware[];
|
|
11
11
|
basePattern: string;
|
|
12
|
+
/** Optional path matcher for global middleware (basePattern ''). Only routes matching one of these patterns get this middleware. E.g. ['protected/*', 'admin'] */
|
|
13
|
+
pathPatterns?: string[];
|
|
12
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Returns true if routePattern matches a single path pattern.
|
|
17
|
+
* Pattern may be exact ('admin') or prefix with * ('admin/*'). No leading slash.
|
|
18
|
+
*/
|
|
19
|
+
export declare function matchPathPattern(routePattern: string, configPattern: string): boolean;
|
|
13
20
|
/**
|
|
14
21
|
* Returns the middlewares applicable to a route, ordered from most generic to most specific
|
|
15
22
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-applicable-middlewares.d.ts","sourceRoot":"","sources":["../../../src/core/middleware/get-applicable-middlewares.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAG5D;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,UAAU,EAAE,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"get-applicable-middlewares.d.ts","sourceRoot":"","sources":["../../../src/core/middleware/get-applicable-middlewares.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAG5D;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,UAAU,EAAE,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,kKAAkK;IAClK,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAQrF;AA+BD;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,gBAAgB,EAAE,EAC/B,YAAY,EAAE,MAAM,GACnB,UAAU,EAAE,CAUd"}
|
|
@@ -2,35 +2,43 @@
|
|
|
2
2
|
* Determines which middlewares are applicable to a route based on the pattern
|
|
3
3
|
* Core logic - no dependencies
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* Returns true if routePattern matches a single path pattern.
|
|
7
|
+
* Pattern may be exact ('admin') or prefix with * ('admin/*'). No leading slash.
|
|
8
|
+
*/
|
|
9
|
+
export function matchPathPattern(routePattern, configPattern) {
|
|
10
|
+
const route = routePattern.replace(/^\/+|\/+$/g, '');
|
|
11
|
+
const pattern = configPattern.replace(/^\/+|\/+$/g, '');
|
|
12
|
+
if (pattern.endsWith('/*')) {
|
|
13
|
+
const prefix = pattern.slice(0, -2);
|
|
14
|
+
return route === prefix || route.startsWith(prefix + '/');
|
|
15
|
+
}
|
|
16
|
+
return route === pattern || route.startsWith(pattern + '/');
|
|
17
|
+
}
|
|
5
18
|
/**
|
|
6
19
|
* Checks if a middleware base pattern applies to a route pattern
|
|
7
20
|
* A middleware applies if its basePattern is a prefix of the routePattern
|
|
8
|
-
*
|
|
9
|
-
* Examples:
|
|
10
|
-
* - basePattern: "" → applies to all routes (global)
|
|
11
|
-
* - basePattern: "posts" → applies to "posts", "posts/:id", "posts/:id/comments", etc
|
|
12
|
-
* - basePattern: "posts/:id" → applies to "posts/:id", "posts/:id/comments", but not to "posts"
|
|
21
|
+
* For global middleware (basePattern '') with pathPatterns, applies only if route matches one of them.
|
|
13
22
|
*/
|
|
14
|
-
function isMiddlewareApplicable(
|
|
15
|
-
|
|
23
|
+
function isMiddlewareApplicable(loaded, routePattern) {
|
|
24
|
+
const { basePattern, pathPatterns } = loaded;
|
|
25
|
+
// Global middleware (empty basePattern)
|
|
16
26
|
if (basePattern === '') {
|
|
27
|
+
if (pathPatterns != null && pathPatterns.length > 0) {
|
|
28
|
+
return pathPatterns.some((p) => matchPathPattern(routePattern, p));
|
|
29
|
+
}
|
|
17
30
|
return true;
|
|
18
31
|
}
|
|
19
32
|
// If route is empty (root), only global middleware applies
|
|
20
33
|
if (routePattern === '') {
|
|
21
34
|
return false;
|
|
22
35
|
}
|
|
23
|
-
// Normalize patterns for comparison (remove leading/trailing slashes)
|
|
24
36
|
const normalizedBase = basePattern.replace(/^\/+|\/+$/g, '');
|
|
25
37
|
const normalizedRoute = routePattern.replace(/^\/+|\/+$/g, '');
|
|
26
|
-
|
|
27
|
-
// Or if route starts with basePattern followed by /
|
|
28
|
-
if (normalizedRoute === normalizedBase) {
|
|
38
|
+
if (normalizedRoute === normalizedBase)
|
|
29
39
|
return true;
|
|
30
|
-
|
|
31
|
-
if (normalizedRoute.startsWith(normalizedBase + '/')) {
|
|
40
|
+
if (normalizedRoute.startsWith(normalizedBase + '/'))
|
|
32
41
|
return true;
|
|
33
|
-
}
|
|
34
42
|
return false;
|
|
35
43
|
}
|
|
36
44
|
/**
|
|
@@ -39,7 +47,7 @@ function isMiddlewareApplicable(basePattern, routePattern) {
|
|
|
39
47
|
export function getApplicableMiddlewares(middlewares, routePattern) {
|
|
40
48
|
const applicable = [];
|
|
41
49
|
for (const loadedMiddleware of middlewares) {
|
|
42
|
-
if (isMiddlewareApplicable(loadedMiddleware
|
|
50
|
+
if (isMiddlewareApplicable(loadedMiddleware, routePattern)) {
|
|
43
51
|
applicable.push(...loadedMiddleware.middleware);
|
|
44
52
|
}
|
|
45
53
|
}
|