@contractspec/lib.source-extractors 0.11.0 → 0.13.0

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 (86) hide show
  1. package/dist/browser/codegen/index.js +225 -0
  2. package/dist/browser/extractors/index.js +835 -0
  3. package/dist/browser/index.js +1215 -0
  4. package/dist/browser/types.js +0 -0
  5. package/dist/codegen/index.d.ts +9 -11
  6. package/dist/codegen/index.d.ts.map +1 -1
  7. package/dist/codegen/index.js +223 -14
  8. package/dist/codegen/operation-gen.d.ts +9 -8
  9. package/dist/codegen/operation-gen.d.ts.map +1 -1
  10. package/dist/codegen/registry-gen.d.ts +7 -6
  11. package/dist/codegen/registry-gen.d.ts.map +1 -1
  12. package/dist/codegen/schema-gen.d.ts +9 -8
  13. package/dist/codegen/schema-gen.d.ts.map +1 -1
  14. package/dist/codegen/types.d.ts +29 -32
  15. package/dist/codegen/types.d.ts.map +1 -1
  16. package/dist/detect.d.ts +19 -17
  17. package/dist/detect.d.ts.map +1 -1
  18. package/dist/extract.d.ts +10 -8
  19. package/dist/extract.d.ts.map +1 -1
  20. package/dist/extractors/base.d.ts +76 -75
  21. package/dist/extractors/base.d.ts.map +1 -1
  22. package/dist/extractors/elysia/extractor.d.ts +15 -12
  23. package/dist/extractors/elysia/extractor.d.ts.map +1 -1
  24. package/dist/extractors/express/extractor.d.ts +16 -12
  25. package/dist/extractors/express/extractor.d.ts.map +1 -1
  26. package/dist/extractors/fastify/extractor.d.ts +16 -12
  27. package/dist/extractors/fastify/extractor.d.ts.map +1 -1
  28. package/dist/extractors/hono/extractor.d.ts +15 -12
  29. package/dist/extractors/hono/extractor.d.ts.map +1 -1
  30. package/dist/extractors/index.d.ts +16 -17
  31. package/dist/extractors/index.d.ts.map +1 -1
  32. package/dist/extractors/index.js +834 -40
  33. package/dist/extractors/nestjs/extractor.d.ts +28 -23
  34. package/dist/extractors/nestjs/extractor.d.ts.map +1 -1
  35. package/dist/extractors/next-api/extractor.d.ts +16 -13
  36. package/dist/extractors/next-api/extractor.d.ts.map +1 -1
  37. package/dist/extractors/trpc/extractor.d.ts +16 -12
  38. package/dist/extractors/trpc/extractor.d.ts.map +1 -1
  39. package/dist/extractors/zod/extractor.d.ts +15 -13
  40. package/dist/extractors/zod/extractor.d.ts.map +1 -1
  41. package/dist/index.d.ts +30 -7
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +1215 -6
  44. package/dist/node/codegen/index.js +225 -0
  45. package/dist/node/extractors/index.js +835 -0
  46. package/dist/node/index.js +1215 -0
  47. package/dist/node/types.js +0 -0
  48. package/dist/registry.d.ts +69 -68
  49. package/dist/registry.d.ts.map +1 -1
  50. package/dist/types.d.ts +182 -185
  51. package/dist/types.d.ts.map +1 -1
  52. package/dist/types.js +1 -0
  53. package/package.json +60 -21
  54. package/dist/_virtual/_rolldown/runtime.js +0 -18
  55. package/dist/codegen/index.js.map +0 -1
  56. package/dist/codegen/operation-gen.js +0 -91
  57. package/dist/codegen/operation-gen.js.map +0 -1
  58. package/dist/codegen/registry-gen.js +0 -47
  59. package/dist/codegen/registry-gen.js.map +0 -1
  60. package/dist/codegen/schema-gen.js +0 -93
  61. package/dist/codegen/schema-gen.js.map +0 -1
  62. package/dist/detect.js +0 -177
  63. package/dist/detect.js.map +0 -1
  64. package/dist/extract.js +0 -125
  65. package/dist/extract.js.map +0 -1
  66. package/dist/extractors/base.js +0 -152
  67. package/dist/extractors/base.js.map +0 -1
  68. package/dist/extractors/elysia/extractor.js +0 -58
  69. package/dist/extractors/elysia/extractor.js.map +0 -1
  70. package/dist/extractors/express/extractor.js +0 -61
  71. package/dist/extractors/express/extractor.js.map +0 -1
  72. package/dist/extractors/fastify/extractor.js +0 -61
  73. package/dist/extractors/fastify/extractor.js.map +0 -1
  74. package/dist/extractors/hono/extractor.js +0 -57
  75. package/dist/extractors/hono/extractor.js.map +0 -1
  76. package/dist/extractors/index.js.map +0 -1
  77. package/dist/extractors/nestjs/extractor.js +0 -118
  78. package/dist/extractors/nestjs/extractor.js.map +0 -1
  79. package/dist/extractors/next-api/extractor.js +0 -80
  80. package/dist/extractors/next-api/extractor.js.map +0 -1
  81. package/dist/extractors/trpc/extractor.js +0 -71
  82. package/dist/extractors/trpc/extractor.js.map +0 -1
  83. package/dist/extractors/zod/extractor.js +0 -69
  84. package/dist/extractors/zod/extractor.js.map +0 -1
  85. package/dist/registry.js +0 -87
  86. package/dist/registry.js.map +0 -1
@@ -1,118 +0,0 @@
1
- import { BaseExtractor } from "../base.js";
2
-
3
- //#region src/extractors/nestjs/extractor.ts
4
- /**
5
- * NestJS extractor.
6
- *
7
- * Extracts contract candidates from NestJS applications by parsing:
8
- * - @Controller() decorated classes
9
- * - @Get(), @Post(), etc. decorated methods
10
- * - Class-validator DTOs
11
- * - HttpException patterns
12
- */
13
- /**
14
- * Regex patterns for NestJS detection and extraction.
15
- */
16
- const PATTERNS = {
17
- controller: /@Controller\s*\(\s*['"`]([^'"`]*)['"`]\s*\)/g,
18
- route: /@(Get|Post|Put|Patch|Delete|Head|Options)\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g,
19
- body: /@Body\s*\(\s*\)/g,
20
- param: /@Param\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g,
21
- query: /@Query\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g,
22
- dto: /class\s+(\w+(?:Dto|DTO|Request|Response|Input|Output))\s*\{/g,
23
- classValidator: /@(IsString|IsNumber|IsBoolean|IsArray|IsOptional|IsNotEmpty|Min|Max|Length|Matches)/g
24
- };
25
- /**
26
- * NestJS framework extractor.
27
- */
28
- var NestJsExtractor = class extends BaseExtractor {
29
- id = "nestjs";
30
- name = "NestJS Extractor";
31
- frameworks = ["nestjs"];
32
- priority = 20;
33
- async doExtract(ctx) {
34
- const { project, options, fs } = ctx;
35
- const pattern = options.scope?.length ? options.scope.map((s) => `${s}/**/*.ts`).join(",") : "**/*.ts";
36
- const files = await fs.glob(pattern, { cwd: project.rootPath });
37
- ctx.ir.stats.filesScanned = files.length;
38
- for (const file of files) {
39
- if (file.includes("node_modules") || file.includes(".spec.") || file.includes(".test.")) continue;
40
- const fullPath = `${project.rootPath}/${file}`;
41
- const content = await fs.readFile(fullPath);
42
- await this.extractControllers(ctx, file, content);
43
- await this.extractDtos(ctx, file, content);
44
- }
45
- }
46
- /**
47
- * Extract controllers and their routes from a file.
48
- */
49
- async extractControllers(ctx, file, content) {
50
- const controllerMatches = [...content.matchAll(PATTERNS.controller)];
51
- for (const controllerMatch of controllerMatches) {
52
- const basePath = controllerMatch[1] || "";
53
- const controllerIndex = controllerMatch.index ?? 0;
54
- const controllerName = content.slice(controllerIndex).match(/class\s+(\w+)/)?.[1] ?? "UnknownController";
55
- const nextController = content.indexOf("@Controller", controllerIndex + 1);
56
- const controllerBlock = nextController > 0 ? content.slice(controllerIndex, nextController) : content.slice(controllerIndex);
57
- const routeMatches = [...controllerBlock.matchAll(PATTERNS.route)];
58
- for (const routeMatch of routeMatches) {
59
- const method = routeMatch[1]?.toUpperCase();
60
- const routePath = routeMatch[2] || "";
61
- const fullPath = this.normalizePath(`/${basePath}/${routePath}`);
62
- const afterRoute = controllerBlock.slice(routeMatch.index ?? 0);
63
- const handlerName = afterRoute.match(/(?:async\s+)?(\w+)\s*\([^)]*\)\s*(?::\s*\w+(?:<[^>]+>)?)?\s*\{/)?.[1] ?? "unknownHandler";
64
- const absoluteIndex = controllerIndex + (routeMatch.index ?? 0);
65
- const lineNumber = content.slice(0, absoluteIndex).split("\n").length;
66
- const hasBody = PATTERNS.body.test(afterRoute.slice(0, 200));
67
- const hasParams = PATTERNS.param.test(afterRoute.slice(0, 200));
68
- const hasQuery = PATTERNS.query.test(afterRoute.slice(0, 200));
69
- const endpoint = {
70
- id: this.generateEndpointId(method, fullPath, handlerName),
71
- method,
72
- path: fullPath,
73
- kind: this.methodToOpKind(method),
74
- handlerName,
75
- controllerName,
76
- source: this.createLocation(file, lineNumber, lineNumber + 10),
77
- confidence: this.createConfidence("medium", "decorator-hints"),
78
- frameworkMeta: {
79
- hasBody,
80
- hasParams,
81
- hasQuery
82
- }
83
- };
84
- this.addEndpoint(ctx, endpoint);
85
- }
86
- }
87
- }
88
- /**
89
- * Extract DTO classes from a file.
90
- */
91
- async extractDtos(ctx, file, content) {
92
- const dtoMatches = [...content.matchAll(PATTERNS.dto)];
93
- for (const match of dtoMatches) {
94
- const name = match[1] ?? "UnknownDto";
95
- const index = match.index ?? 0;
96
- const lineNumber = content.slice(0, index).split("\n").length;
97
- const hasClassValidator = content.includes("class-validator") || content.includes("@IsString") || content.includes("@IsNumber");
98
- const schema = {
99
- id: this.generateSchemaId(name, file),
100
- name,
101
- schemaType: hasClassValidator ? "class-validator" : "typescript",
102
- source: this.createLocation(file, lineNumber, lineNumber + 20),
103
- confidence: this.createConfidence(hasClassValidator ? "high" : "medium", hasClassValidator ? "explicit-schema" : "inferred-types")
104
- };
105
- this.addSchema(ctx, schema);
106
- }
107
- }
108
- /**
109
- * Normalize a path (remove double slashes, ensure leading slash).
110
- */
111
- normalizePath(path) {
112
- return "/" + path.replace(/\/+/g, "/").replace(/^\/+|\/+$/g, "");
113
- }
114
- };
115
-
116
- //#endregion
117
- export { NestJsExtractor };
118
- //# sourceMappingURL=extractor.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extractor.js","names":[],"sources":["../../../src/extractors/nestjs/extractor.ts"],"sourcesContent":["/**\n * NestJS extractor.\n *\n * Extracts contract candidates from NestJS applications by parsing:\n * - @Controller() decorated classes\n * - @Get(), @Post(), etc. decorated methods\n * - Class-validator DTOs\n * - HttpException patterns\n */\n\nimport { BaseExtractor, type ExtractionContext } from '../base';\nimport type {\n EndpointCandidate,\n HttpMethod,\n SchemaCandidate,\n} from '../../types';\n\n/**\n * Regex patterns for NestJS detection and extraction.\n */\nconst PATTERNS = {\n controller: /@Controller\\s*\\(\\s*['\"`]([^'\"`]*)['\"`]\\s*\\)/g,\n route:\n /@(Get|Post|Put|Patch|Delete|Head|Options)\\s*\\(\\s*(?:['\"`]([^'\"`]*)['\"`])?\\s*\\)/g,\n body: /@Body\\s*\\(\\s*\\)/g,\n param: /@Param\\s*\\(\\s*(?:['\"`]([^'\"`]*)['\"`])?\\s*\\)/g,\n query: /@Query\\s*\\(\\s*(?:['\"`]([^'\"`]*)['\"`])?\\s*\\)/g,\n dto: /class\\s+(\\w+(?:Dto|DTO|Request|Response|Input|Output))\\s*\\{/g,\n classValidator:\n /@(IsString|IsNumber|IsBoolean|IsArray|IsOptional|IsNotEmpty|Min|Max|Length|Matches)/g,\n};\n\n/**\n * NestJS framework extractor.\n */\nexport class NestJsExtractor extends BaseExtractor {\n id = 'nestjs';\n name = 'NestJS Extractor';\n frameworks = ['nestjs'];\n priority = 20; // Higher priority for NestJS-specific detection\n\n protected async doExtract(ctx: ExtractionContext): Promise<void> {\n const { project, options, fs } = ctx;\n\n // Find TypeScript files\n const pattern = options.scope?.length\n ? options.scope.map((s) => `${s}/**/*.ts`).join(',')\n : '**/*.ts';\n\n const files = await fs.glob(pattern, { cwd: project.rootPath });\n ctx.ir.stats.filesScanned = files.length;\n\n for (const file of files) {\n // Skip test files and node_modules\n if (\n file.includes('node_modules') ||\n file.includes('.spec.') ||\n file.includes('.test.')\n ) {\n continue;\n }\n\n const fullPath = `${project.rootPath}/${file}`;\n const content = await fs.readFile(fullPath);\n\n // Extract controllers and routes\n await this.extractControllers(ctx, file, content);\n\n // Extract DTOs\n await this.extractDtos(ctx, file, content);\n }\n }\n\n /**\n * Extract controllers and their routes from a file.\n */\n private async extractControllers(\n ctx: ExtractionContext,\n file: string,\n content: string\n ): Promise<void> {\n // Find controllers\n const controllerMatches = [...content.matchAll(PATTERNS.controller)];\n\n for (const controllerMatch of controllerMatches) {\n const basePath = controllerMatch[1] || '';\n const controllerIndex = controllerMatch.index ?? 0;\n\n // Find the class name after the decorator\n const afterDecorator = content.slice(controllerIndex);\n const classMatch = afterDecorator.match(/class\\s+(\\w+)/);\n const controllerName = classMatch?.[1] ?? 'UnknownController';\n\n // Find route decorators in this controller\n // We need to find routes between this controller and the next one (or end of file)\n const nextController = content.indexOf(\n '@Controller',\n controllerIndex + 1\n );\n const controllerBlock =\n nextController > 0\n ? content.slice(controllerIndex, nextController)\n : content.slice(controllerIndex);\n\n const routeMatches = [...controllerBlock.matchAll(PATTERNS.route)];\n\n for (const routeMatch of routeMatches) {\n const method = routeMatch[1]?.toUpperCase() as HttpMethod;\n const routePath = routeMatch[2] || '';\n const fullPath = this.normalizePath(`/${basePath}/${routePath}`);\n\n // Find the method name after the route decorator\n const afterRoute = controllerBlock.slice(routeMatch.index ?? 0);\n const methodMatch = afterRoute.match(\n /(?:async\\s+)?(\\w+)\\s*\\([^)]*\\)\\s*(?::\\s*\\w+(?:<[^>]+>)?)?\\s*\\{/\n );\n const handlerName = methodMatch?.[1] ?? 'unknownHandler';\n\n // Calculate line numbers\n const absoluteIndex = controllerIndex + (routeMatch.index ?? 0);\n const lineNumber = content.slice(0, absoluteIndex).split('\\n').length;\n\n // Check for body, params, query decorators\n const hasBody = PATTERNS.body.test(afterRoute.slice(0, 200));\n const hasParams = PATTERNS.param.test(afterRoute.slice(0, 200));\n const hasQuery = PATTERNS.query.test(afterRoute.slice(0, 200));\n\n const endpoint: EndpointCandidate = {\n id: this.generateEndpointId(method, fullPath, handlerName),\n method,\n path: fullPath,\n kind: this.methodToOpKind(method),\n handlerName,\n controllerName,\n source: this.createLocation(file, lineNumber, lineNumber + 10),\n confidence: this.createConfidence('medium', 'decorator-hints'),\n frameworkMeta: {\n hasBody,\n hasParams,\n hasQuery,\n },\n };\n\n this.addEndpoint(ctx, endpoint);\n }\n }\n }\n\n /**\n * Extract DTO classes from a file.\n */\n private async extractDtos(\n ctx: ExtractionContext,\n file: string,\n content: string\n ): Promise<void> {\n const dtoMatches = [...content.matchAll(PATTERNS.dto)];\n\n for (const match of dtoMatches) {\n const name = match[1] ?? 'UnknownDto';\n const index = match.index ?? 0;\n const lineNumber = content.slice(0, index).split('\\n').length;\n\n // Check if it uses class-validator (check for import statement)\n const hasClassValidator =\n content.includes('class-validator') ||\n content.includes('@IsString') ||\n content.includes('@IsNumber');\n\n const schema: SchemaCandidate = {\n id: this.generateSchemaId(name, file),\n name,\n schemaType: hasClassValidator ? 'class-validator' : 'typescript',\n source: this.createLocation(file, lineNumber, lineNumber + 20),\n confidence: this.createConfidence(\n hasClassValidator ? 'high' : 'medium',\n hasClassValidator ? 'explicit-schema' : 'inferred-types'\n ),\n };\n\n this.addSchema(ctx, schema);\n }\n }\n\n /**\n * Normalize a path (remove double slashes, ensure leading slash).\n */\n private normalizePath(path: string): string {\n return '/' + path.replace(/\\/+/g, '/').replace(/^\\/+|\\/+$/g, '');\n }\n}\n"],"mappings":"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"}
@@ -1,80 +0,0 @@
1
- import { BaseExtractor } from "../base.js";
2
-
3
- //#region src/extractors/next-api/extractor.ts
4
- /**
5
- * Next.js API Routes extractor.
6
- *
7
- * Extracts contract candidates from Next.js applications by parsing:
8
- * - App Router API routes (app/api/.../route.ts)
9
- * - Pages Router API routes (pages/api/.../*.ts)
10
- */
11
- const PATTERNS = {
12
- appRouterExport: /export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/gi,
13
- pagesHandler: /export\s+default\s+(?:async\s+)?function/g,
14
- zodSchema: /z\.\w+\(/g
15
- };
16
- var NextApiExtractor = class extends BaseExtractor {
17
- id = "next-api";
18
- name = "Next.js API Extractor";
19
- frameworks = ["next-api"];
20
- priority = 15;
21
- async doExtract(ctx) {
22
- const { project, fs } = ctx;
23
- const appRoutes = await fs.glob("**/app/api/**/route.ts", { cwd: project.rootPath });
24
- const pagesRoutes = await fs.glob("**/pages/api/**/*.ts", { cwd: project.rootPath });
25
- const allRoutes = [...appRoutes, ...pagesRoutes];
26
- ctx.ir.stats.filesScanned = allRoutes.length;
27
- for (const file of allRoutes) {
28
- const fullPath = `${project.rootPath}/${file}`;
29
- const content = await fs.readFile(fullPath);
30
- if (file.includes("/app/api/")) await this.extractAppRoutes(ctx, file, content);
31
- else await this.extractPagesRoutes(ctx, file, content);
32
- }
33
- }
34
- async extractAppRoutes(ctx, file, content) {
35
- const pathMatch = file.match(/app\/api\/(.+)\/route\.ts$/);
36
- const routePath = pathMatch ? `/api/${pathMatch[1]}` : "/api";
37
- const matches = [...content.matchAll(PATTERNS.appRouterExport)];
38
- for (const match of matches) {
39
- const method = match[1]?.toUpperCase() ?? "GET";
40
- const index = match.index ?? 0;
41
- const lineNumber = content.slice(0, index).split("\n").length;
42
- const hasZod = PATTERNS.zodSchema.test(content);
43
- const endpoint = {
44
- id: this.generateEndpointId(method, routePath),
45
- method,
46
- path: routePath.replace(/\[(\w+)\]/g, ":$1"),
47
- kind: this.methodToOpKind(method),
48
- handlerName: method,
49
- source: this.createLocation(file, lineNumber, lineNumber + 10),
50
- confidence: this.createConfidence(hasZod ? "high" : "medium", hasZod ? "explicit-schema" : "inferred-types"),
51
- frameworkMeta: { routeType: "app-router" }
52
- };
53
- this.addEndpoint(ctx, endpoint);
54
- }
55
- }
56
- async extractPagesRoutes(ctx, file, content) {
57
- const pathMatch = file.match(/pages\/api\/(.+)\.ts$/);
58
- const routePath = pathMatch ? `/api/${pathMatch[1]}` : "/api";
59
- if (!PATTERNS.pagesHandler.test(content)) return;
60
- const lineNumber = 1;
61
- PATTERNS.zodSchema.test(content);
62
- for (const method of ["GET", "POST"]) {
63
- const endpoint = {
64
- id: this.generateEndpointId(method, routePath),
65
- method,
66
- path: routePath.replace(/\[(\w+)\]/g, ":$1"),
67
- kind: this.methodToOpKind(method),
68
- handlerName: "handler",
69
- source: this.createLocation(file, lineNumber, lineNumber + 20),
70
- confidence: this.createConfidence("low", "naming-convention"),
71
- frameworkMeta: { routeType: "pages-router" }
72
- };
73
- this.addEndpoint(ctx, endpoint);
74
- }
75
- }
76
- };
77
-
78
- //#endregion
79
- export { NextApiExtractor };
80
- //# sourceMappingURL=extractor.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extractor.js","names":[],"sources":["../../../src/extractors/next-api/extractor.ts"],"sourcesContent":["/**\n * Next.js API Routes extractor.\n *\n * Extracts contract candidates from Next.js applications by parsing:\n * - App Router API routes (app/api/.../route.ts)\n * - Pages Router API routes (pages/api/.../*.ts)\n */\n\nimport { BaseExtractor, type ExtractionContext } from '../base';\nimport type { EndpointCandidate, HttpMethod } from '../../types';\n\nconst PATTERNS = {\n appRouterExport:\n /export\\s+(?:async\\s+)?function\\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/gi,\n pagesHandler: /export\\s+default\\s+(?:async\\s+)?function/g,\n zodSchema: /z\\.\\w+\\(/g,\n};\n\nexport class NextApiExtractor extends BaseExtractor {\n id = 'next-api';\n name = 'Next.js API Extractor';\n frameworks = ['next-api'];\n priority = 15;\n\n protected async doExtract(ctx: ExtractionContext): Promise<void> {\n const { project, fs } = ctx;\n\n // Find App Router API routes\n const appRoutes = await fs.glob('**/app/api/**/route.ts', {\n cwd: project.rootPath,\n });\n // Find Pages Router API routes\n const pagesRoutes = await fs.glob('**/pages/api/**/*.ts', {\n cwd: project.rootPath,\n });\n\n const allRoutes = [...appRoutes, ...pagesRoutes];\n ctx.ir.stats.filesScanned = allRoutes.length;\n\n for (const file of allRoutes) {\n const fullPath = `${project.rootPath}/${file}`;\n const content = await fs.readFile(fullPath);\n\n if (file.includes('/app/api/')) {\n await this.extractAppRoutes(ctx, file, content);\n } else {\n await this.extractPagesRoutes(ctx, file, content);\n }\n }\n }\n\n private async extractAppRoutes(\n ctx: ExtractionContext,\n file: string,\n content: string\n ): Promise<void> {\n // Derive path from file path\n const pathMatch = file.match(/app\\/api\\/(.+)\\/route\\.ts$/);\n const routePath = pathMatch ? `/api/${pathMatch[1]}` : '/api';\n\n const matches = [...content.matchAll(PATTERNS.appRouterExport)];\n\n for (const match of matches) {\n const method = (match[1]?.toUpperCase() ?? 'GET') as HttpMethod;\n const index = match.index ?? 0;\n const lineNumber = content.slice(0, index).split('\\n').length;\n\n const hasZod = PATTERNS.zodSchema.test(content);\n\n const endpoint: EndpointCandidate = {\n id: this.generateEndpointId(method, routePath),\n method,\n path: routePath.replace(/\\[(\\w+)\\]/g, ':$1'),\n kind: this.methodToOpKind(method),\n handlerName: method,\n source: this.createLocation(file, lineNumber, lineNumber + 10),\n confidence: this.createConfidence(\n hasZod ? 'high' : 'medium',\n hasZod ? 'explicit-schema' : 'inferred-types'\n ),\n frameworkMeta: { routeType: 'app-router' },\n };\n\n this.addEndpoint(ctx, endpoint);\n }\n }\n\n private async extractPagesRoutes(\n ctx: ExtractionContext,\n file: string,\n content: string\n ): Promise<void> {\n const pathMatch = file.match(/pages\\/api\\/(.+)\\.ts$/);\n const routePath = pathMatch ? `/api/${pathMatch[1]}` : '/api';\n\n if (!PATTERNS.pagesHandler.test(content)) return;\n\n const lineNumber = 1;\n const _hasZod = PATTERNS.zodSchema.test(content);\n\n // Pages API routes can handle multiple methods\n const methods: HttpMethod[] = ['GET', 'POST'];\n\n for (const method of methods) {\n const endpoint: EndpointCandidate = {\n id: this.generateEndpointId(method, routePath),\n method,\n path: routePath.replace(/\\[(\\w+)\\]/g, ':$1'),\n kind: this.methodToOpKind(method),\n handlerName: 'handler',\n source: this.createLocation(file, lineNumber, lineNumber + 20),\n confidence: this.createConfidence('low', 'naming-convention'),\n frameworkMeta: { routeType: 'pages-router' },\n };\n\n this.addEndpoint(ctx, endpoint);\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAWA,MAAM,WAAW;CACf,iBACE;CACF,cAAc;CACd,WAAW;CACZ;AAED,IAAa,mBAAb,cAAsC,cAAc;CAClD,KAAK;CACL,OAAO;CACP,aAAa,CAAC,WAAW;CACzB,WAAW;CAEX,MAAgB,UAAU,KAAuC;EAC/D,MAAM,EAAE,SAAS,OAAO;EAGxB,MAAM,YAAY,MAAM,GAAG,KAAK,0BAA0B,EACxD,KAAK,QAAQ,UACd,CAAC;EAEF,MAAM,cAAc,MAAM,GAAG,KAAK,wBAAwB,EACxD,KAAK,QAAQ,UACd,CAAC;EAEF,MAAM,YAAY,CAAC,GAAG,WAAW,GAAG,YAAY;AAChD,MAAI,GAAG,MAAM,eAAe,UAAU;AAEtC,OAAK,MAAM,QAAQ,WAAW;GAC5B,MAAM,WAAW,GAAG,QAAQ,SAAS,GAAG;GACxC,MAAM,UAAU,MAAM,GAAG,SAAS,SAAS;AAE3C,OAAI,KAAK,SAAS,YAAY,CAC5B,OAAM,KAAK,iBAAiB,KAAK,MAAM,QAAQ;OAE/C,OAAM,KAAK,mBAAmB,KAAK,MAAM,QAAQ;;;CAKvD,MAAc,iBACZ,KACA,MACA,SACe;EAEf,MAAM,YAAY,KAAK,MAAM,6BAA6B;EAC1D,MAAM,YAAY,YAAY,QAAQ,UAAU,OAAO;EAEvD,MAAM,UAAU,CAAC,GAAG,QAAQ,SAAS,SAAS,gBAAgB,CAAC;AAE/D,OAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,SAAU,MAAM,IAAI,aAAa,IAAI;GAC3C,MAAM,QAAQ,MAAM,SAAS;GAC7B,MAAM,aAAa,QAAQ,MAAM,GAAG,MAAM,CAAC,MAAM,KAAK,CAAC;GAEvD,MAAM,SAAS,SAAS,UAAU,KAAK,QAAQ;GAE/C,MAAM,WAA8B;IAClC,IAAI,KAAK,mBAAmB,QAAQ,UAAU;IAC9C;IACA,MAAM,UAAU,QAAQ,cAAc,MAAM;IAC5C,MAAM,KAAK,eAAe,OAAO;IACjC,aAAa;IACb,QAAQ,KAAK,eAAe,MAAM,YAAY,aAAa,GAAG;IAC9D,YAAY,KAAK,iBACf,SAAS,SAAS,UAClB,SAAS,oBAAoB,iBAC9B;IACD,eAAe,EAAE,WAAW,cAAc;IAC3C;AAED,QAAK,YAAY,KAAK,SAAS;;;CAInC,MAAc,mBACZ,KACA,MACA,SACe;EACf,MAAM,YAAY,KAAK,MAAM,wBAAwB;EACrD,MAAM,YAAY,YAAY,QAAQ,UAAU,OAAO;AAEvD,MAAI,CAAC,SAAS,aAAa,KAAK,QAAQ,CAAE;EAE1C,MAAM,aAAa;AACH,WAAS,UAAU,KAAK,QAAQ;AAKhD,OAAK,MAAM,UAFmB,CAAC,OAAO,OAAO,EAEf;GAC5B,MAAM,WAA8B;IAClC,IAAI,KAAK,mBAAmB,QAAQ,UAAU;IAC9C;IACA,MAAM,UAAU,QAAQ,cAAc,MAAM;IAC5C,MAAM,KAAK,eAAe,OAAO;IACjC,aAAa;IACb,QAAQ,KAAK,eAAe,MAAM,YAAY,aAAa,GAAG;IAC9D,YAAY,KAAK,iBAAiB,OAAO,oBAAoB;IAC7D,eAAe,EAAE,WAAW,gBAAgB;IAC7C;AAED,QAAK,YAAY,KAAK,SAAS"}
@@ -1,71 +0,0 @@
1
- import { BaseExtractor } from "../base.js";
2
-
3
- //#region src/extractors/trpc/extractor.ts
4
- /**
5
- * tRPC extractor.
6
- *
7
- * Extracts contract candidates from tRPC applications by parsing:
8
- * - router definitions
9
- * - procedure definitions (query, mutation)
10
- * - Zod input/output schemas
11
- */
12
- const PATTERNS = {
13
- procedure: /\.(query|mutation)\s*\(\s*(?:\{[^}]*\}|[^)]+)\)/gi,
14
- procedureName: /(\w+)\s*:\s*(?:publicProcedure|protectedProcedure|procedure)/g,
15
- zodInput: /\.input\s*\(\s*(\w+)/g,
16
- zodOutput: /\.output\s*\(\s*(\w+)/g
17
- };
18
- var TrpcExtractor = class extends BaseExtractor {
19
- id = "trpc";
20
- name = "tRPC Extractor";
21
- frameworks = ["trpc"];
22
- priority = 15;
23
- async doExtract(ctx) {
24
- const { project, options, fs } = ctx;
25
- const pattern = options.scope?.length ? options.scope.map((s) => `${s}/**/*.ts`).join(",") : "**/*.ts";
26
- const files = await fs.glob(pattern, { cwd: project.rootPath });
27
- ctx.ir.stats.filesScanned = files.length;
28
- for (const file of files) {
29
- if (file.includes("node_modules") || file.includes(".test.")) continue;
30
- const fullPath = `${project.rootPath}/${file}`;
31
- const content = await fs.readFile(fullPath);
32
- if (!content.includes("trpc") && !content.includes("Procedure")) continue;
33
- await this.extractProcedures(ctx, file, content);
34
- }
35
- }
36
- async extractProcedures(ctx, file, content) {
37
- const nameMatches = [...content.matchAll(PATTERNS.procedureName)];
38
- for (const match of nameMatches) {
39
- const procedureName = match[1] ?? "unknownProcedure";
40
- const index = match.index ?? 0;
41
- const lineNumber = content.slice(0, index).split("\n").length;
42
- const afterMatch = content.slice(index, index + 500);
43
- const isQuery = afterMatch.includes(".query(");
44
- const isMutation = afterMatch.includes(".mutation(");
45
- if (!isQuery && !isMutation) continue;
46
- const hasZodInput = PATTERNS.zodInput.test(afterMatch);
47
- const hasZodOutput = PATTERNS.zodOutput.test(afterMatch);
48
- const hasSchema = hasZodInput || hasZodOutput;
49
- const method = isMutation ? "POST" : "GET";
50
- const endpoint = {
51
- id: `trpc.${procedureName}`,
52
- method,
53
- path: `/trpc/${procedureName}`,
54
- kind: isMutation ? "command" : "query",
55
- handlerName: procedureName,
56
- source: this.createLocation(file, lineNumber, lineNumber + 10),
57
- confidence: this.createConfidence(hasSchema ? "high" : "medium", hasSchema ? "explicit-schema" : "inferred-types"),
58
- frameworkMeta: {
59
- procedureType: isMutation ? "mutation" : "query",
60
- hasInput: hasZodInput,
61
- hasOutput: hasZodOutput
62
- }
63
- };
64
- this.addEndpoint(ctx, endpoint);
65
- }
66
- }
67
- };
68
-
69
- //#endregion
70
- export { TrpcExtractor };
71
- //# sourceMappingURL=extractor.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extractor.js","names":[],"sources":["../../../src/extractors/trpc/extractor.ts"],"sourcesContent":["/**\n * tRPC extractor.\n *\n * Extracts contract candidates from tRPC applications by parsing:\n * - router definitions\n * - procedure definitions (query, mutation)\n * - Zod input/output schemas\n */\n\nimport { BaseExtractor, type ExtractionContext } from '../base';\nimport type { EndpointCandidate, HttpMethod } from '../../types';\n\nconst PATTERNS = {\n procedure: /\\.(query|mutation)\\s*\\(\\s*(?:\\{[^}]*\\}|[^)]+)\\)/gi,\n procedureName:\n /(\\w+)\\s*:\\s*(?:publicProcedure|protectedProcedure|procedure)/g,\n zodInput: /\\.input\\s*\\(\\s*(\\w+)/g,\n zodOutput: /\\.output\\s*\\(\\s*(\\w+)/g,\n};\n\nexport class TrpcExtractor extends BaseExtractor {\n id = 'trpc';\n name = 'tRPC Extractor';\n frameworks = ['trpc'];\n priority = 15;\n\n protected async doExtract(ctx: ExtractionContext): Promise<void> {\n const { project, options, fs } = ctx;\n\n const pattern = options.scope?.length\n ? options.scope.map((s) => `${s}/**/*.ts`).join(',')\n : '**/*.ts';\n\n const files = await fs.glob(pattern, { cwd: project.rootPath });\n ctx.ir.stats.filesScanned = files.length;\n\n for (const file of files) {\n if (file.includes('node_modules') || file.includes('.test.')) continue;\n\n const fullPath = `${project.rootPath}/${file}`;\n const content = await fs.readFile(fullPath);\n\n if (!content.includes('trpc') && !content.includes('Procedure')) continue;\n\n await this.extractProcedures(ctx, file, content);\n }\n }\n\n private async extractProcedures(\n ctx: ExtractionContext,\n file: string,\n content: string\n ): Promise<void> {\n const nameMatches = [...content.matchAll(PATTERNS.procedureName)];\n\n for (const match of nameMatches) {\n const procedureName = match[1] ?? 'unknownProcedure';\n const index = match.index ?? 0;\n const lineNumber = content.slice(0, index).split('\\n').length;\n\n const afterMatch = content.slice(index, index + 500);\n const isQuery = afterMatch.includes('.query(');\n const isMutation = afterMatch.includes('.mutation(');\n\n if (!isQuery && !isMutation) continue;\n\n const hasZodInput = PATTERNS.zodInput.test(afterMatch);\n const hasZodOutput = PATTERNS.zodOutput.test(afterMatch);\n const hasSchema = hasZodInput || hasZodOutput;\n\n const method: HttpMethod = isMutation ? 'POST' : 'GET';\n\n const endpoint: EndpointCandidate = {\n id: `trpc.${procedureName}`,\n method,\n path: `/trpc/${procedureName}`,\n kind: isMutation ? 'command' : 'query',\n handlerName: procedureName,\n source: this.createLocation(file, lineNumber, lineNumber + 10),\n confidence: this.createConfidence(\n hasSchema ? 'high' : 'medium',\n hasSchema ? 'explicit-schema' : 'inferred-types'\n ),\n frameworkMeta: {\n procedureType: isMutation ? 'mutation' : 'query',\n hasInput: hasZodInput,\n hasOutput: hasZodOutput,\n },\n };\n\n this.addEndpoint(ctx, endpoint);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;AAYA,MAAM,WAAW;CACf,WAAW;CACX,eACE;CACF,UAAU;CACV,WAAW;CACZ;AAED,IAAa,gBAAb,cAAmC,cAAc;CAC/C,KAAK;CACL,OAAO;CACP,aAAa,CAAC,OAAO;CACrB,WAAW;CAEX,MAAgB,UAAU,KAAuC;EAC/D,MAAM,EAAE,SAAS,SAAS,OAAO;EAEjC,MAAM,UAAU,QAAQ,OAAO,SAC3B,QAAQ,MAAM,KAAK,MAAM,GAAG,EAAE,UAAU,CAAC,KAAK,IAAI,GAClD;EAEJ,MAAM,QAAQ,MAAM,GAAG,KAAK,SAAS,EAAE,KAAK,QAAQ,UAAU,CAAC;AAC/D,MAAI,GAAG,MAAM,eAAe,MAAM;AAElC,OAAK,MAAM,QAAQ,OAAO;AACxB,OAAI,KAAK,SAAS,eAAe,IAAI,KAAK,SAAS,SAAS,CAAE;GAE9D,MAAM,WAAW,GAAG,QAAQ,SAAS,GAAG;GACxC,MAAM,UAAU,MAAM,GAAG,SAAS,SAAS;AAE3C,OAAI,CAAC,QAAQ,SAAS,OAAO,IAAI,CAAC,QAAQ,SAAS,YAAY,CAAE;AAEjE,SAAM,KAAK,kBAAkB,KAAK,MAAM,QAAQ;;;CAIpD,MAAc,kBACZ,KACA,MACA,SACe;EACf,MAAM,cAAc,CAAC,GAAG,QAAQ,SAAS,SAAS,cAAc,CAAC;AAEjE,OAAK,MAAM,SAAS,aAAa;GAC/B,MAAM,gBAAgB,MAAM,MAAM;GAClC,MAAM,QAAQ,MAAM,SAAS;GAC7B,MAAM,aAAa,QAAQ,MAAM,GAAG,MAAM,CAAC,MAAM,KAAK,CAAC;GAEvD,MAAM,aAAa,QAAQ,MAAM,OAAO,QAAQ,IAAI;GACpD,MAAM,UAAU,WAAW,SAAS,UAAU;GAC9C,MAAM,aAAa,WAAW,SAAS,aAAa;AAEpD,OAAI,CAAC,WAAW,CAAC,WAAY;GAE7B,MAAM,cAAc,SAAS,SAAS,KAAK,WAAW;GACtD,MAAM,eAAe,SAAS,UAAU,KAAK,WAAW;GACxD,MAAM,YAAY,eAAe;GAEjC,MAAM,SAAqB,aAAa,SAAS;GAEjD,MAAM,WAA8B;IAClC,IAAI,QAAQ;IACZ;IACA,MAAM,SAAS;IACf,MAAM,aAAa,YAAY;IAC/B,aAAa;IACb,QAAQ,KAAK,eAAe,MAAM,YAAY,aAAa,GAAG;IAC9D,YAAY,KAAK,iBACf,YAAY,SAAS,UACrB,YAAY,oBAAoB,iBACjC;IACD,eAAe;KACb,eAAe,aAAa,aAAa;KACzC,UAAU;KACV,WAAW;KACZ;IACF;AAED,QAAK,YAAY,KAAK,SAAS"}
@@ -1,69 +0,0 @@
1
- import { BaseExtractor } from "../base.js";
2
-
3
- //#region src/extractors/zod/extractor.ts
4
- /**
5
- * Zod schema extractor.
6
- *
7
- * Extracts standalone Zod schema definitions that aren't tied to a specific framework.
8
- * Useful for shared validation schemas.
9
- */
10
- const PATTERNS = {
11
- zodSchema: /(?:export\s+)?const\s+(\w+)\s*=\s*z\.(?:object|string|number|boolean|array|enum|union|intersection|literal|tuple|record)/g,
12
- zodInfer: /type\s+(\w+)\s*=\s*z\.infer<typeof\s+(\w+)>/g
13
- };
14
- var ZodSchemaExtractor = class extends BaseExtractor {
15
- id = "zod";
16
- name = "Zod Schema Extractor";
17
- frameworks = ["zod"];
18
- priority = 5;
19
- async detect() {
20
- return true;
21
- }
22
- async doExtract(ctx) {
23
- const { project, options, fs } = ctx;
24
- const pattern = options.scope?.length ? options.scope.map((s) => `${s}/**/*.ts`).join(",") : "**/*.ts";
25
- const files = await fs.glob(pattern, { cwd: project.rootPath });
26
- ctx.ir.stats.filesScanned = files.length;
27
- for (const file of files) {
28
- if (file.includes("node_modules") || file.includes(".test.")) continue;
29
- const fullPath = `${project.rootPath}/${file}`;
30
- const content = await fs.readFile(fullPath);
31
- if (!content.includes("z.")) continue;
32
- await this.extractSchemas(ctx, file, content);
33
- }
34
- }
35
- async extractSchemas(ctx, file, content) {
36
- const matches = [...content.matchAll(PATTERNS.zodSchema)];
37
- for (const match of matches) {
38
- const name = match[1] ?? "unknownSchema";
39
- const index = match.index ?? 0;
40
- const lineNumber = content.slice(0, index).split("\n").length;
41
- let depth = 0;
42
- let endIndex = index;
43
- for (let i = index; i < content.length; i++) {
44
- const char = content[i];
45
- if (char === "(" || char === "{" || char === "[") depth++;
46
- if (char === ")" || char === "}" || char === "]") depth--;
47
- if (depth === 0 && (char === ";" || char === "\n")) {
48
- endIndex = i;
49
- break;
50
- }
51
- }
52
- const rawDefinition = content.slice(index, endIndex + 1);
53
- const endLineNumber = lineNumber + rawDefinition.split("\n").length - 1;
54
- const schema = {
55
- id: this.generateSchemaId(name, file),
56
- name,
57
- schemaType: "zod",
58
- rawDefinition,
59
- source: this.createLocation(file, lineNumber, endLineNumber),
60
- confidence: this.createConfidence("high", "explicit-schema")
61
- };
62
- this.addSchema(ctx, schema);
63
- }
64
- }
65
- };
66
-
67
- //#endregion
68
- export { ZodSchemaExtractor };
69
- //# sourceMappingURL=extractor.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extractor.js","names":[],"sources":["../../../src/extractors/zod/extractor.ts"],"sourcesContent":["/**\n * Zod schema extractor.\n *\n * Extracts standalone Zod schema definitions that aren't tied to a specific framework.\n * Useful for shared validation schemas.\n */\n\nimport { BaseExtractor, type ExtractionContext } from '../base';\nimport type { SchemaCandidate } from '../../types';\n\nconst PATTERNS = {\n zodSchema:\n /(?:export\\s+)?const\\s+(\\w+)\\s*=\\s*z\\.(?:object|string|number|boolean|array|enum|union|intersection|literal|tuple|record)/g,\n zodInfer: /type\\s+(\\w+)\\s*=\\s*z\\.infer<typeof\\s+(\\w+)>/g,\n};\n\nexport class ZodSchemaExtractor extends BaseExtractor {\n id = 'zod';\n name = 'Zod Schema Extractor';\n frameworks = ['zod'];\n priority = 5; // Lower priority, runs after framework extractors\n\n async detect(): Promise<boolean> {\n // Always available as a fallback\n return true;\n }\n\n protected async doExtract(ctx: ExtractionContext): Promise<void> {\n const { project, options, fs } = ctx;\n\n const pattern = options.scope?.length\n ? options.scope.map((s) => `${s}/**/*.ts`).join(',')\n : '**/*.ts';\n\n const files = await fs.glob(pattern, { cwd: project.rootPath });\n ctx.ir.stats.filesScanned = files.length;\n\n for (const file of files) {\n if (file.includes('node_modules') || file.includes('.test.')) continue;\n\n const fullPath = `${project.rootPath}/${file}`;\n const content = await fs.readFile(fullPath);\n\n if (!content.includes('z.')) continue;\n\n await this.extractSchemas(ctx, file, content);\n }\n }\n\n private async extractSchemas(\n ctx: ExtractionContext,\n file: string,\n content: string\n ): Promise<void> {\n const matches = [...content.matchAll(PATTERNS.zodSchema)];\n\n for (const match of matches) {\n const name = match[1] ?? 'unknownSchema';\n const index = match.index ?? 0;\n const lineNumber = content.slice(0, index).split('\\n').length;\n\n // Find the end of the schema definition (roughly)\n let depth = 0;\n let endIndex = index;\n for (let i = index; i < content.length; i++) {\n const char = content[i];\n if (char === '(' || char === '{' || char === '[') depth++;\n if (char === ')' || char === '}' || char === ']') depth--;\n if (depth === 0 && (char === ';' || char === '\\n')) {\n endIndex = i;\n break;\n }\n }\n\n const rawDefinition = content.slice(index, endIndex + 1);\n const endLineNumber = lineNumber + rawDefinition.split('\\n').length - 1;\n\n const schema: SchemaCandidate = {\n id: this.generateSchemaId(name, file),\n name,\n schemaType: 'zod',\n rawDefinition,\n source: this.createLocation(file, lineNumber, endLineNumber),\n confidence: this.createConfidence('high', 'explicit-schema'),\n };\n\n this.addSchema(ctx, schema);\n }\n }\n}\n"],"mappings":";;;;;;;;;AAUA,MAAM,WAAW;CACf,WACE;CACF,UAAU;CACX;AAED,IAAa,qBAAb,cAAwC,cAAc;CACpD,KAAK;CACL,OAAO;CACP,aAAa,CAAC,MAAM;CACpB,WAAW;CAEX,MAAM,SAA2B;AAE/B,SAAO;;CAGT,MAAgB,UAAU,KAAuC;EAC/D,MAAM,EAAE,SAAS,SAAS,OAAO;EAEjC,MAAM,UAAU,QAAQ,OAAO,SAC3B,QAAQ,MAAM,KAAK,MAAM,GAAG,EAAE,UAAU,CAAC,KAAK,IAAI,GAClD;EAEJ,MAAM,QAAQ,MAAM,GAAG,KAAK,SAAS,EAAE,KAAK,QAAQ,UAAU,CAAC;AAC/D,MAAI,GAAG,MAAM,eAAe,MAAM;AAElC,OAAK,MAAM,QAAQ,OAAO;AACxB,OAAI,KAAK,SAAS,eAAe,IAAI,KAAK,SAAS,SAAS,CAAE;GAE9D,MAAM,WAAW,GAAG,QAAQ,SAAS,GAAG;GACxC,MAAM,UAAU,MAAM,GAAG,SAAS,SAAS;AAE3C,OAAI,CAAC,QAAQ,SAAS,KAAK,CAAE;AAE7B,SAAM,KAAK,eAAe,KAAK,MAAM,QAAQ;;;CAIjD,MAAc,eACZ,KACA,MACA,SACe;EACf,MAAM,UAAU,CAAC,GAAG,QAAQ,SAAS,SAAS,UAAU,CAAC;AAEzD,OAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,OAAO,MAAM,MAAM;GACzB,MAAM,QAAQ,MAAM,SAAS;GAC7B,MAAM,aAAa,QAAQ,MAAM,GAAG,MAAM,CAAC,MAAM,KAAK,CAAC;GAGvD,IAAI,QAAQ;GACZ,IAAI,WAAW;AACf,QAAK,IAAI,IAAI,OAAO,IAAI,QAAQ,QAAQ,KAAK;IAC3C,MAAM,OAAO,QAAQ;AACrB,QAAI,SAAS,OAAO,SAAS,OAAO,SAAS,IAAK;AAClD,QAAI,SAAS,OAAO,SAAS,OAAO,SAAS,IAAK;AAClD,QAAI,UAAU,MAAM,SAAS,OAAO,SAAS,OAAO;AAClD,gBAAW;AACX;;;GAIJ,MAAM,gBAAgB,QAAQ,MAAM,OAAO,WAAW,EAAE;GACxD,MAAM,gBAAgB,aAAa,cAAc,MAAM,KAAK,CAAC,SAAS;GAEtE,MAAM,SAA0B;IAC9B,IAAI,KAAK,iBAAiB,MAAM,KAAK;IACrC;IACA,YAAY;IACZ;IACA,QAAQ,KAAK,eAAe,MAAM,YAAY,cAAc;IAC5D,YAAY,KAAK,iBAAiB,QAAQ,kBAAkB;IAC7D;AAED,QAAK,UAAU,KAAK,OAAO"}
package/dist/registry.js DELETED
@@ -1,87 +0,0 @@
1
- //#region src/registry.ts
2
- /**
3
- * Registry for source extractor plugins.
4
- */
5
- var ExtractorRegistry = class {
6
- extractors = /* @__PURE__ */ new Map();
7
- /**
8
- * Register an extractor plugin.
9
- */
10
- register(extractor) {
11
- this.extractors.set(extractor.id, extractor);
12
- }
13
- /**
14
- * Unregister an extractor plugin.
15
- */
16
- unregister(id) {
17
- return this.extractors.delete(id);
18
- }
19
- /**
20
- * Get an extractor by ID.
21
- */
22
- get(id) {
23
- return this.extractors.get(id);
24
- }
25
- /**
26
- * Get all registered extractors.
27
- */
28
- getAll() {
29
- return Array.from(this.extractors.values());
30
- }
31
- /**
32
- * Find extractors that can handle the given project.
33
- * Returns extractors sorted by priority (highest first).
34
- */
35
- async findMatching(project) {
36
- const matches = [];
37
- for (const extractor of this.extractors.values()) try {
38
- if (await extractor.detect(project)) matches.push(extractor);
39
- } catch {}
40
- return matches.sort((a, b) => b.priority - a.priority);
41
- }
42
- /**
43
- * Find extractors for a specific framework.
44
- */
45
- findByFramework(framework) {
46
- const matches = [];
47
- for (const extractor of this.extractors.values()) if (extractor.frameworks.includes(framework)) matches.push(extractor);
48
- return matches.sort((a, b) => b.priority - a.priority);
49
- }
50
- /**
51
- * Alias for findByFramework.
52
- */
53
- findForFramework(framework) {
54
- return this.findByFramework(framework);
55
- }
56
- /**
57
- * Check if an extractor exists for the given framework.
58
- */
59
- hasExtractorFor(framework) {
60
- for (const extractor of this.extractors.values()) if (extractor.frameworks.includes(framework) || extractor.id === framework) return true;
61
- return false;
62
- }
63
- /**
64
- * Get list of all supported framework IDs.
65
- */
66
- getSupportedFrameworks() {
67
- const frameworks = /* @__PURE__ */ new Set();
68
- for (const extractor of this.extractors.values()) {
69
- frameworks.add(extractor.id);
70
- for (const fw of extractor.frameworks) frameworks.add(fw);
71
- }
72
- return Array.from(frameworks);
73
- }
74
- };
75
- /**
76
- * Global extractor registry instance.
77
- */
78
- const extractorRegistry = new ExtractorRegistry();
79
- /**
80
- * Register built-in extractors.
81
- * Called automatically when the module is imported.
82
- */
83
- function registerBuiltInExtractors() {}
84
-
85
- //#endregion
86
- export { ExtractorRegistry, extractorRegistry, registerBuiltInExtractors };
87
- //# sourceMappingURL=registry.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"registry.js","names":[],"sources":["../src/registry.ts"],"sourcesContent":["/**\n * Source extractor registry.\n *\n * Manages extractor plugins and provides lookup by framework.\n */\n\nimport type { ExtractOptions, ExtractResult, ProjectInfo } from './types';\n\n/**\n * Interface that all source extractors must implement.\n */\nexport interface SourceExtractor {\n /** Unique identifier for this extractor */\n id: string;\n /** Human-readable name */\n name: string;\n /** Frameworks this extractor supports */\n frameworks: string[];\n /** Priority (higher = preferred when multiple match) */\n priority: number;\n\n /**\n * Detect if this extractor can handle the given project.\n * @param project Project information\n * @returns true if this extractor should be used\n */\n detect(project: ProjectInfo): Promise<boolean>;\n\n /**\n * Extract contract candidates from the project.\n * @param project Project information\n * @param options Extraction options\n * @returns Extraction result with IR\n */\n extract(\n project: ProjectInfo,\n options: ExtractOptions\n ): Promise<ExtractResult>;\n}\n\n/**\n * Registry for source extractor plugins.\n */\nexport class ExtractorRegistry {\n private extractors = new Map<string, SourceExtractor>();\n\n /**\n * Register an extractor plugin.\n */\n register(extractor: SourceExtractor): void {\n this.extractors.set(extractor.id, extractor);\n }\n\n /**\n * Unregister an extractor plugin.\n */\n unregister(id: string): boolean {\n return this.extractors.delete(id);\n }\n\n /**\n * Get an extractor by ID.\n */\n get(id: string): SourceExtractor | undefined {\n return this.extractors.get(id);\n }\n\n /**\n * Get all registered extractors.\n */\n getAll(): SourceExtractor[] {\n return Array.from(this.extractors.values());\n }\n\n /**\n * Find extractors that can handle the given project.\n * Returns extractors sorted by priority (highest first).\n */\n async findMatching(project: ProjectInfo): Promise<SourceExtractor[]> {\n const matches: SourceExtractor[] = [];\n\n for (const extractor of this.extractors.values()) {\n try {\n if (await extractor.detect(project)) {\n matches.push(extractor);\n }\n } catch {\n // Skip extractors that fail detection\n }\n }\n\n return matches.sort((a, b) => b.priority - a.priority);\n }\n\n /**\n * Find extractors for a specific framework.\n */\n findByFramework(framework: string): SourceExtractor[] {\n const matches: SourceExtractor[] = [];\n\n for (const extractor of this.extractors.values()) {\n if (extractor.frameworks.includes(framework)) {\n matches.push(extractor);\n }\n }\n\n return matches.sort((a, b) => b.priority - a.priority);\n }\n\n /**\n * Alias for findByFramework.\n */\n findForFramework(framework: string): SourceExtractor[] {\n return this.findByFramework(framework);\n }\n\n /**\n * Check if an extractor exists for the given framework.\n */\n hasExtractorFor(framework: string): boolean {\n for (const extractor of this.extractors.values()) {\n if (\n extractor.frameworks.includes(framework) ||\n extractor.id === framework\n ) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Get list of all supported framework IDs.\n */\n getSupportedFrameworks(): string[] {\n const frameworks = new Set<string>();\n for (const extractor of this.extractors.values()) {\n frameworks.add(extractor.id);\n for (const fw of extractor.frameworks) {\n frameworks.add(fw);\n }\n }\n return Array.from(frameworks);\n }\n}\n\n/**\n * Global extractor registry instance.\n */\nexport const extractorRegistry = new ExtractorRegistry();\n\n/**\n * Register built-in extractors.\n * Called automatically when the module is imported.\n */\nexport function registerBuiltInExtractors(): void {\n // Built-in extractors are registered in extractors/index.ts\n // This function is called after all extractors are imported\n}\n"],"mappings":";;;;AA2CA,IAAa,oBAAb,MAA+B;CAC7B,AAAQ,6BAAa,IAAI,KAA8B;;;;CAKvD,SAAS,WAAkC;AACzC,OAAK,WAAW,IAAI,UAAU,IAAI,UAAU;;;;;CAM9C,WAAW,IAAqB;AAC9B,SAAO,KAAK,WAAW,OAAO,GAAG;;;;;CAMnC,IAAI,IAAyC;AAC3C,SAAO,KAAK,WAAW,IAAI,GAAG;;;;;CAMhC,SAA4B;AAC1B,SAAO,MAAM,KAAK,KAAK,WAAW,QAAQ,CAAC;;;;;;CAO7C,MAAM,aAAa,SAAkD;EACnE,MAAM,UAA6B,EAAE;AAErC,OAAK,MAAM,aAAa,KAAK,WAAW,QAAQ,CAC9C,KAAI;AACF,OAAI,MAAM,UAAU,OAAO,QAAQ,CACjC,SAAQ,KAAK,UAAU;UAEnB;AAKV,SAAO,QAAQ,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,SAAS;;;;;CAMxD,gBAAgB,WAAsC;EACpD,MAAM,UAA6B,EAAE;AAErC,OAAK,MAAM,aAAa,KAAK,WAAW,QAAQ,CAC9C,KAAI,UAAU,WAAW,SAAS,UAAU,CAC1C,SAAQ,KAAK,UAAU;AAI3B,SAAO,QAAQ,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,SAAS;;;;;CAMxD,iBAAiB,WAAsC;AACrD,SAAO,KAAK,gBAAgB,UAAU;;;;;CAMxC,gBAAgB,WAA4B;AAC1C,OAAK,MAAM,aAAa,KAAK,WAAW,QAAQ,CAC9C,KACE,UAAU,WAAW,SAAS,UAAU,IACxC,UAAU,OAAO,UAEjB,QAAO;AAGX,SAAO;;;;;CAMT,yBAAmC;EACjC,MAAM,6BAAa,IAAI,KAAa;AACpC,OAAK,MAAM,aAAa,KAAK,WAAW,QAAQ,EAAE;AAChD,cAAW,IAAI,UAAU,GAAG;AAC5B,QAAK,MAAM,MAAM,UAAU,WACzB,YAAW,IAAI,GAAG;;AAGtB,SAAO,MAAM,KAAK,WAAW;;;;;;AAOjC,MAAa,oBAAoB,IAAI,mBAAmB;;;;;AAMxD,SAAgB,4BAAkC"}