@contractspec/lib.source-extractors 0.2.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 (78) hide show
  1. package/README.md +86 -0
  2. package/dist/_virtual/rolldown_runtime.js +18 -0
  3. package/dist/codegen/index.d.ts +12 -0
  4. package/dist/codegen/index.d.ts.map +1 -0
  5. package/dist/codegen/index.js +17 -0
  6. package/dist/codegen/index.js.map +1 -0
  7. package/dist/codegen/operation-gen.d.ts +16 -0
  8. package/dist/codegen/operation-gen.d.ts.map +1 -0
  9. package/dist/codegen/operation-gen.js +91 -0
  10. package/dist/codegen/operation-gen.js.map +1 -0
  11. package/dist/codegen/registry-gen.d.ts +11 -0
  12. package/dist/codegen/registry-gen.d.ts.map +1 -0
  13. package/dist/codegen/registry-gen.js +47 -0
  14. package/dist/codegen/registry-gen.js.map +1 -0
  15. package/dist/codegen/schema-gen.d.ts +16 -0
  16. package/dist/codegen/schema-gen.d.ts.map +1 -0
  17. package/dist/codegen/schema-gen.js +93 -0
  18. package/dist/codegen/schema-gen.js.map +1 -0
  19. package/dist/codegen/types.d.ts +48 -0
  20. package/dist/codegen/types.d.ts.map +1 -0
  21. package/dist/detect.d.ts +47 -0
  22. package/dist/detect.d.ts.map +1 -0
  23. package/dist/detect.js +177 -0
  24. package/dist/detect.js.map +1 -0
  25. package/dist/extract.d.ts +24 -0
  26. package/dist/extract.d.ts.map +1 -0
  27. package/dist/extract.js +125 -0
  28. package/dist/extract.js.map +1 -0
  29. package/dist/extractors/base.d.ts +90 -0
  30. package/dist/extractors/base.d.ts.map +1 -0
  31. package/dist/extractors/base.js +152 -0
  32. package/dist/extractors/base.js.map +1 -0
  33. package/dist/extractors/elysia/extractor.d.ts +15 -0
  34. package/dist/extractors/elysia/extractor.d.ts.map +1 -0
  35. package/dist/extractors/elysia/extractor.js +58 -0
  36. package/dist/extractors/elysia/extractor.js.map +1 -0
  37. package/dist/extractors/express/extractor.d.ts +15 -0
  38. package/dist/extractors/express/extractor.d.ts.map +1 -0
  39. package/dist/extractors/express/extractor.js +61 -0
  40. package/dist/extractors/express/extractor.js.map +1 -0
  41. package/dist/extractors/fastify/extractor.d.ts +15 -0
  42. package/dist/extractors/fastify/extractor.d.ts.map +1 -0
  43. package/dist/extractors/fastify/extractor.js +61 -0
  44. package/dist/extractors/fastify/extractor.js.map +1 -0
  45. package/dist/extractors/hono/extractor.d.ts +15 -0
  46. package/dist/extractors/hono/extractor.d.ts.map +1 -0
  47. package/dist/extractors/hono/extractor.js +57 -0
  48. package/dist/extractors/hono/extractor.js.map +1 -0
  49. package/dist/extractors/index.d.ts +21 -0
  50. package/dist/extractors/index.d.ts.map +1 -0
  51. package/dist/extractors/index.js +42 -0
  52. package/dist/extractors/index.js.map +1 -0
  53. package/dist/extractors/nestjs/extractor.d.ts +29 -0
  54. package/dist/extractors/nestjs/extractor.d.ts.map +1 -0
  55. package/dist/extractors/nestjs/extractor.js +118 -0
  56. package/dist/extractors/nestjs/extractor.js.map +1 -0
  57. package/dist/extractors/next-api/extractor.d.ts +16 -0
  58. package/dist/extractors/next-api/extractor.d.ts.map +1 -0
  59. package/dist/extractors/next-api/extractor.js +80 -0
  60. package/dist/extractors/next-api/extractor.js.map +1 -0
  61. package/dist/extractors/trpc/extractor.d.ts +15 -0
  62. package/dist/extractors/trpc/extractor.d.ts.map +1 -0
  63. package/dist/extractors/trpc/extractor.js +71 -0
  64. package/dist/extractors/trpc/extractor.js.map +1 -0
  65. package/dist/extractors/zod/extractor.d.ts +16 -0
  66. package/dist/extractors/zod/extractor.d.ts.map +1 -0
  67. package/dist/extractors/zod/extractor.js +69 -0
  68. package/dist/extractors/zod/extractor.js.map +1 -0
  69. package/dist/index.d.ts +7 -0
  70. package/dist/index.js +7 -0
  71. package/dist/registry.d.ts +85 -0
  72. package/dist/registry.d.ts.map +1 -0
  73. package/dist/registry.js +87 -0
  74. package/dist/registry.js.map +1 -0
  75. package/dist/types.d.ts +272 -0
  76. package/dist/types.d.ts.map +1 -0
  77. package/dist/types.js +0 -0
  78. package/package.json +71 -0
@@ -0,0 +1,58 @@
1
+ import { BaseExtractor } from "../base.js";
2
+
3
+ //#region src/extractors/elysia/extractor.ts
4
+ /**
5
+ * Elysia extractor.
6
+ *
7
+ * Extracts contract candidates from Elysia applications by parsing:
8
+ * - app.get(), app.post(), etc. route handlers
9
+ * - t.* schema definitions
10
+ */
11
+ const PATTERNS = {
12
+ route: /\.(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
13
+ tSchema: /body:\s*t\.\w+/g
14
+ };
15
+ var ElysiaExtractor = class extends BaseExtractor {
16
+ id = "elysia";
17
+ name = "Elysia Extractor";
18
+ frameworks = ["elysia"];
19
+ priority = 15;
20
+ async doExtract(ctx) {
21
+ const { project, options, fs } = ctx;
22
+ const pattern = options.scope?.length ? options.scope.map((s) => `${s}/**/*.ts`).join(",") : "**/*.ts";
23
+ const files = await fs.glob(pattern, { cwd: project.rootPath });
24
+ ctx.ir.stats.filesScanned = files.length;
25
+ for (const file of files) {
26
+ if (file.includes("node_modules") || file.includes(".test.")) continue;
27
+ const fullPath = `${project.rootPath}/${file}`;
28
+ const content = await fs.readFile(fullPath);
29
+ if (!content.includes("elysia")) continue;
30
+ await this.extractRoutes(ctx, file, content);
31
+ }
32
+ }
33
+ async extractRoutes(ctx, file, content) {
34
+ const matches = [...content.matchAll(PATTERNS.route)];
35
+ for (const match of matches) {
36
+ const method = match[1]?.toUpperCase() ?? "GET";
37
+ const path = match[2] ?? "/";
38
+ const index = match.index ?? 0;
39
+ const lineNumber = content.slice(0, index).split("\n").length;
40
+ const afterMatch = content.slice(index, index + 500);
41
+ const hasTSchema = PATTERNS.tSchema.test(afterMatch);
42
+ const endpoint = {
43
+ id: this.generateEndpointId(method, path),
44
+ method,
45
+ path,
46
+ kind: this.methodToOpKind(method),
47
+ handlerName: "handler",
48
+ source: this.createLocation(file, lineNumber, lineNumber + 5),
49
+ confidence: this.createConfidence(hasTSchema ? "high" : "medium", hasTSchema ? "explicit-schema" : "decorator-hints")
50
+ };
51
+ this.addEndpoint(ctx, endpoint);
52
+ }
53
+ }
54
+ };
55
+
56
+ //#endregion
57
+ export { ElysiaExtractor };
58
+ //# sourceMappingURL=extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor.js","names":[],"sources":["../../../src/extractors/elysia/extractor.ts"],"sourcesContent":["/**\n * Elysia extractor.\n *\n * Extracts contract candidates from Elysia applications by parsing:\n * - app.get(), app.post(), etc. route handlers\n * - t.* schema definitions\n */\n\nimport { BaseExtractor, type ExtractionContext } from '../base';\nimport type { EndpointCandidate, HttpMethod } from '../../types';\n\nconst PATTERNS = {\n route: /\\.(get|post|put|patch|delete)\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/gi,\n tSchema: /body:\\s*t\\.\\w+/g,\n};\n\nexport class ElysiaExtractor extends BaseExtractor {\n id = 'elysia';\n name = 'Elysia Extractor';\n frameworks = ['elysia'];\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('elysia')) continue;\n\n await this.extractRoutes(ctx, file, content);\n }\n }\n\n private async extractRoutes(\n ctx: ExtractionContext,\n file: string,\n content: string\n ): Promise<void> {\n const matches = [...content.matchAll(PATTERNS.route)];\n\n for (const match of matches) {\n const method = (match[1]?.toUpperCase() ?? 'GET') as HttpMethod;\n const path = match[2] ?? '/';\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 hasTSchema = PATTERNS.tSchema.test(afterMatch);\n\n const endpoint: EndpointCandidate = {\n id: this.generateEndpointId(method, path),\n method,\n path,\n kind: this.methodToOpKind(method),\n handlerName: 'handler',\n source: this.createLocation(file, lineNumber, lineNumber + 5),\n confidence: this.createConfidence(\n hasTSchema ? 'high' : 'medium',\n hasTSchema ? 'explicit-schema' : 'decorator-hints'\n ),\n };\n\n this.addEndpoint(ctx, endpoint);\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAWA,MAAM,WAAW;CACf,OAAO;CACP,SAAS;CACV;AAED,IAAa,kBAAb,cAAqC,cAAc;CACjD,KAAK;CACL,OAAO;CACP,aAAa,CAAC,SAAS;CACvB,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,SAAS,CAAE;AAEjC,SAAM,KAAK,cAAc,KAAK,MAAM,QAAQ;;;CAIhD,MAAc,cACZ,KACA,MACA,SACe;EACf,MAAM,UAAU,CAAC,GAAG,QAAQ,SAAS,SAAS,MAAM,CAAC;AAErD,OAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,SAAU,MAAM,IAAI,aAAa,IAAI;GAC3C,MAAM,OAAO,MAAM,MAAM;GACzB,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,aAAa,SAAS,QAAQ,KAAK,WAAW;GAEpD,MAAM,WAA8B;IAClC,IAAI,KAAK,mBAAmB,QAAQ,KAAK;IACzC;IACA;IACA,MAAM,KAAK,eAAe,OAAO;IACjC,aAAa;IACb,QAAQ,KAAK,eAAe,MAAM,YAAY,aAAa,EAAE;IAC7D,YAAY,KAAK,iBACf,aAAa,SAAS,UACtB,aAAa,oBAAoB,kBAClC;IACF;AAED,QAAK,YAAY,KAAK,SAAS"}
@@ -0,0 +1,15 @@
1
+ import { BaseExtractor, ExtractionContext } from "../base.js";
2
+
3
+ //#region src/extractors/express/extractor.d.ts
4
+
5
+ declare class ExpressExtractor extends BaseExtractor {
6
+ id: string;
7
+ name: string;
8
+ frameworks: string[];
9
+ priority: number;
10
+ protected doExtract(ctx: ExtractionContext): Promise<void>;
11
+ private extractRoutes;
12
+ }
13
+ //#endregion
14
+ export { ExpressExtractor };
15
+ //# sourceMappingURL=extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor.d.ts","names":[],"sources":["../../../src/extractors/express/extractor.ts"],"sourcesContent":[],"mappings":";;;;cAmBa,gBAAA,SAAyB,aAAA;;;;;2BAML,oBAAoB"}
@@ -0,0 +1,61 @@
1
+ import { BaseExtractor } from "../base.js";
2
+
3
+ //#region src/extractors/express/extractor.ts
4
+ /**
5
+ * Express extractor.
6
+ *
7
+ * Extracts contract candidates from Express applications by parsing:
8
+ * - app.get(), app.post(), etc. route handlers
9
+ * - Router instances
10
+ * - Zod validation middleware
11
+ */
12
+ const PATTERNS = {
13
+ route: /(?:app|router)\.(get|post|put|patch|delete|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
14
+ routerUse: /(?:app|router)\.use\s*\(\s*['"`]([^'"`]+)['"`]/gi,
15
+ zodValidate: /validate\s*\(\s*(\w+)\s*\)/g
16
+ };
17
+ var ExpressExtractor = class extends BaseExtractor {
18
+ id = "express";
19
+ name = "Express Extractor";
20
+ frameworks = ["express"];
21
+ priority = 15;
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
+ await this.extractRoutes(ctx, file, content);
32
+ }
33
+ }
34
+ async extractRoutes(ctx, file, content) {
35
+ const matches = [...content.matchAll(PATTERNS.route)];
36
+ for (const match of matches) {
37
+ const method = match[1]?.toUpperCase() ?? "GET";
38
+ const path = match[2] ?? "/";
39
+ const index = match.index ?? 0;
40
+ const lineNumber = content.slice(0, index).split("\n").length;
41
+ const afterMatch = content.slice(index, index + 500);
42
+ const handlerMatch = afterMatch.match(/(?:async\s+)?(?:function\s+)?(\w+)|,\s*(\w+)\s*\)/);
43
+ const handlerName = handlerMatch?.[1] ?? handlerMatch?.[2] ?? "handler";
44
+ const hasZodValidation = PATTERNS.zodValidate.test(afterMatch);
45
+ const endpoint = {
46
+ id: this.generateEndpointId(method, path, handlerName),
47
+ method,
48
+ path,
49
+ kind: this.methodToOpKind(method),
50
+ handlerName,
51
+ source: this.createLocation(file, lineNumber, lineNumber + 5),
52
+ confidence: this.createConfidence(hasZodValidation ? "high" : "medium", hasZodValidation ? "explicit-schema" : "decorator-hints")
53
+ };
54
+ this.addEndpoint(ctx, endpoint);
55
+ }
56
+ }
57
+ };
58
+
59
+ //#endregion
60
+ export { ExpressExtractor };
61
+ //# sourceMappingURL=extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor.js","names":[],"sources":["../../../src/extractors/express/extractor.ts"],"sourcesContent":["/**\n * Express extractor.\n *\n * Extracts contract candidates from Express applications by parsing:\n * - app.get(), app.post(), etc. route handlers\n * - Router instances\n * - Zod validation middleware\n */\n\nimport { BaseExtractor, type ExtractionContext } from '../base';\nimport type { EndpointCandidate, HttpMethod } from '../../types';\n\nconst PATTERNS = {\n route:\n /(?:app|router)\\.(get|post|put|patch|delete|head|options)\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/gi,\n routerUse: /(?:app|router)\\.use\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/gi,\n zodValidate: /validate\\s*\\(\\s*(\\w+)\\s*\\)/g,\n};\n\nexport class ExpressExtractor extends BaseExtractor {\n id = 'express';\n name = 'Express Extractor';\n frameworks = ['express'];\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 await this.extractRoutes(ctx, file, content);\n }\n }\n\n private async extractRoutes(\n ctx: ExtractionContext,\n file: string,\n content: string\n ): Promise<void> {\n const matches = [...content.matchAll(PATTERNS.route)];\n\n for (const match of matches) {\n const method = (match[1]?.toUpperCase() ?? 'GET') as HttpMethod;\n const path = match[2] ?? '/';\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 handlerMatch = afterMatch.match(\n /(?:async\\s+)?(?:function\\s+)?(\\w+)|,\\s*(\\w+)\\s*\\)/\n );\n const handlerName = handlerMatch?.[1] ?? handlerMatch?.[2] ?? 'handler';\n\n const hasZodValidation = PATTERNS.zodValidate.test(afterMatch);\n\n const endpoint: EndpointCandidate = {\n id: this.generateEndpointId(method, path, handlerName),\n method,\n path,\n kind: this.methodToOpKind(method),\n handlerName,\n source: this.createLocation(file, lineNumber, lineNumber + 5),\n confidence: this.createConfidence(\n hasZodValidation ? 'high' : 'medium',\n hasZodValidation ? 'explicit-schema' : 'decorator-hints'\n ),\n };\n\n this.addEndpoint(ctx, endpoint);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;AAYA,MAAM,WAAW;CACf,OACE;CACF,WAAW;CACX,aAAa;CACd;AAED,IAAa,mBAAb,cAAsC,cAAc;CAClD,KAAK;CACL,OAAO;CACP,aAAa,CAAC,UAAU;CACxB,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,SAAM,KAAK,cAAc,KAAK,MAAM,QAAQ;;;CAIhD,MAAc,cACZ,KACA,MACA,SACe;EACf,MAAM,UAAU,CAAC,GAAG,QAAQ,SAAS,SAAS,MAAM,CAAC;AAErD,OAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,SAAU,MAAM,IAAI,aAAa,IAAI;GAC3C,MAAM,OAAO,MAAM,MAAM;GACzB,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,eAAe,WAAW,MAC9B,oDACD;GACD,MAAM,cAAc,eAAe,MAAM,eAAe,MAAM;GAE9D,MAAM,mBAAmB,SAAS,YAAY,KAAK,WAAW;GAE9D,MAAM,WAA8B;IAClC,IAAI,KAAK,mBAAmB,QAAQ,MAAM,YAAY;IACtD;IACA;IACA,MAAM,KAAK,eAAe,OAAO;IACjC;IACA,QAAQ,KAAK,eAAe,MAAM,YAAY,aAAa,EAAE;IAC7D,YAAY,KAAK,iBACf,mBAAmB,SAAS,UAC5B,mBAAmB,oBAAoB,kBACxC;IACF;AAED,QAAK,YAAY,KAAK,SAAS"}
@@ -0,0 +1,15 @@
1
+ import { BaseExtractor, ExtractionContext } from "../base.js";
2
+
3
+ //#region src/extractors/fastify/extractor.d.ts
4
+
5
+ declare class FastifyExtractor extends BaseExtractor {
6
+ id: string;
7
+ name: string;
8
+ frameworks: string[];
9
+ priority: number;
10
+ protected doExtract(ctx: ExtractionContext): Promise<void>;
11
+ private extractRoutes;
12
+ }
13
+ //#endregion
14
+ export { FastifyExtractor };
15
+ //# sourceMappingURL=extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor.d.ts","names":[],"sources":["../../../src/extractors/fastify/extractor.ts"],"sourcesContent":[],"mappings":";;;;cAoBa,gBAAA,SAAyB,aAAA;;;;;2BAML,oBAAoB"}
@@ -0,0 +1,61 @@
1
+ import { BaseExtractor } from "../base.js";
2
+
3
+ //#region src/extractors/fastify/extractor.ts
4
+ /**
5
+ * Fastify extractor.
6
+ *
7
+ * Extracts contract candidates from Fastify applications by parsing:
8
+ * - fastify.get(), fastify.post(), etc. route handlers
9
+ * - JSON Schema definitions
10
+ * - Route options with schema
11
+ */
12
+ const PATTERNS = {
13
+ route: /(?:fastify|app|server)\.(get|post|put|patch|delete|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
14
+ schemaOption: /schema\s*:\s*\{/g,
15
+ bodySchema: /body\s*:\s*(\w+)/g,
16
+ responseSchema: /response\s*:\s*\{/g
17
+ };
18
+ var FastifyExtractor = class extends BaseExtractor {
19
+ id = "fastify";
20
+ name = "Fastify Extractor";
21
+ frameworks = ["fastify"];
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
+ await this.extractRoutes(ctx, file, content);
33
+ }
34
+ }
35
+ async extractRoutes(ctx, file, content) {
36
+ const matches = [...content.matchAll(PATTERNS.route)];
37
+ for (const match of matches) {
38
+ const method = match[1]?.toUpperCase() ?? "GET";
39
+ const path = match[2] ?? "/";
40
+ const index = match.index ?? 0;
41
+ const lineNumber = content.slice(0, index).split("\n").length;
42
+ const afterMatch = content.slice(index, index + 1e3);
43
+ const hasSchema = PATTERNS.schemaOption.test(afterMatch);
44
+ const endpoint = {
45
+ id: this.generateEndpointId(method, path),
46
+ method,
47
+ path,
48
+ kind: this.methodToOpKind(method),
49
+ handlerName: "handler",
50
+ source: this.createLocation(file, lineNumber, lineNumber + 10),
51
+ confidence: this.createConfidence(hasSchema ? "high" : "medium", hasSchema ? "explicit-schema" : "decorator-hints"),
52
+ frameworkMeta: { hasSchema }
53
+ };
54
+ this.addEndpoint(ctx, endpoint);
55
+ }
56
+ }
57
+ };
58
+
59
+ //#endregion
60
+ export { FastifyExtractor };
61
+ //# sourceMappingURL=extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor.js","names":[],"sources":["../../../src/extractors/fastify/extractor.ts"],"sourcesContent":["/**\n * Fastify extractor.\n *\n * Extracts contract candidates from Fastify applications by parsing:\n * - fastify.get(), fastify.post(), etc. route handlers\n * - JSON Schema definitions\n * - Route options with schema\n */\n\nimport { BaseExtractor, type ExtractionContext } from '../base';\nimport type { EndpointCandidate, HttpMethod } from '../../types';\n\nconst PATTERNS = {\n route:\n /(?:fastify|app|server)\\.(get|post|put|patch|delete|head|options)\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/gi,\n schemaOption: /schema\\s*:\\s*\\{/g,\n bodySchema: /body\\s*:\\s*(\\w+)/g,\n responseSchema: /response\\s*:\\s*\\{/g,\n};\n\nexport class FastifyExtractor extends BaseExtractor {\n id = 'fastify';\n name = 'Fastify Extractor';\n frameworks = ['fastify'];\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 await this.extractRoutes(ctx, file, content);\n }\n }\n\n private async extractRoutes(\n ctx: ExtractionContext,\n file: string,\n content: string\n ): Promise<void> {\n const matches = [...content.matchAll(PATTERNS.route)];\n\n for (const match of matches) {\n const method = (match[1]?.toUpperCase() ?? 'GET') as HttpMethod;\n const path = match[2] ?? '/';\n const index = match.index ?? 0;\n const lineNumber = content.slice(0, index).split('\\n').length;\n\n const afterMatch = content.slice(index, index + 1000);\n const hasSchema = PATTERNS.schemaOption.test(afterMatch);\n\n const endpoint: EndpointCandidate = {\n id: this.generateEndpointId(method, path),\n method,\n path,\n kind: this.methodToOpKind(method),\n handlerName: 'handler',\n source: this.createLocation(file, lineNumber, lineNumber + 10),\n confidence: this.createConfidence(\n hasSchema ? 'high' : 'medium',\n hasSchema ? 'explicit-schema' : 'decorator-hints'\n ),\n frameworkMeta: { hasSchema },\n };\n\n this.addEndpoint(ctx, endpoint);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;AAYA,MAAM,WAAW;CACf,OACE;CACF,cAAc;CACd,YAAY;CACZ,gBAAgB;CACjB;AAED,IAAa,mBAAb,cAAsC,cAAc;CAClD,KAAK;CACL,OAAO;CACP,aAAa,CAAC,UAAU;CACxB,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,SAAM,KAAK,cAAc,KAAK,MAAM,QAAQ;;;CAIhD,MAAc,cACZ,KACA,MACA,SACe;EACf,MAAM,UAAU,CAAC,GAAG,QAAQ,SAAS,SAAS,MAAM,CAAC;AAErD,OAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,SAAU,MAAM,IAAI,aAAa,IAAI;GAC3C,MAAM,OAAO,MAAM,MAAM;GACzB,MAAM,QAAQ,MAAM,SAAS;GAC7B,MAAM,aAAa,QAAQ,MAAM,GAAG,MAAM,CAAC,MAAM,KAAK,CAAC;GAEvD,MAAM,aAAa,QAAQ,MAAM,OAAO,QAAQ,IAAK;GACrD,MAAM,YAAY,SAAS,aAAa,KAAK,WAAW;GAExD,MAAM,WAA8B;IAClC,IAAI,KAAK,mBAAmB,QAAQ,KAAK;IACzC;IACA;IACA,MAAM,KAAK,eAAe,OAAO;IACjC,aAAa;IACb,QAAQ,KAAK,eAAe,MAAM,YAAY,aAAa,GAAG;IAC9D,YAAY,KAAK,iBACf,YAAY,SAAS,UACrB,YAAY,oBAAoB,kBACjC;IACD,eAAe,EAAE,WAAW;IAC7B;AAED,QAAK,YAAY,KAAK,SAAS"}
@@ -0,0 +1,15 @@
1
+ import { BaseExtractor, ExtractionContext } from "../base.js";
2
+
3
+ //#region src/extractors/hono/extractor.d.ts
4
+
5
+ declare class HonoExtractor extends BaseExtractor {
6
+ id: string;
7
+ name: string;
8
+ frameworks: string[];
9
+ priority: number;
10
+ protected doExtract(ctx: ExtractionContext): Promise<void>;
11
+ private extractRoutes;
12
+ }
13
+ //#endregion
14
+ export { HonoExtractor };
15
+ //# sourceMappingURL=extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor.d.ts","names":[],"sources":["../../../src/extractors/hono/extractor.ts"],"sourcesContent":[],"mappings":";;;;cAiBa,aAAA,SAAsB,aAAA;;;;;2BAMF,oBAAoB"}
@@ -0,0 +1,57 @@
1
+ import { BaseExtractor } from "../base.js";
2
+
3
+ //#region src/extractors/hono/extractor.ts
4
+ /**
5
+ * Hono extractor.
6
+ *
7
+ * Extracts contract candidates from Hono applications by parsing:
8
+ * - app.get(), app.post(), etc. route handlers
9
+ * - Zod validator middleware
10
+ */
11
+ const PATTERNS = {
12
+ route: /(?:app|hono)\.(get|post|put|patch|delete|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
13
+ zodValidator: /zValidator\s*\(\s*['"`](\w+)['"`]\s*,\s*(\w+)/g
14
+ };
15
+ var HonoExtractor = class extends BaseExtractor {
16
+ id = "hono";
17
+ name = "Hono Extractor";
18
+ frameworks = ["hono"];
19
+ priority = 15;
20
+ async doExtract(ctx) {
21
+ const { project, options, fs } = ctx;
22
+ const pattern = options.scope?.length ? options.scope.map((s) => `${s}/**/*.ts`).join(",") : "**/*.ts";
23
+ const files = await fs.glob(pattern, { cwd: project.rootPath });
24
+ ctx.ir.stats.filesScanned = files.length;
25
+ for (const file of files) {
26
+ if (file.includes("node_modules") || file.includes(".test.")) continue;
27
+ const fullPath = `${project.rootPath}/${file}`;
28
+ const content = await fs.readFile(fullPath);
29
+ await this.extractRoutes(ctx, file, content);
30
+ }
31
+ }
32
+ async extractRoutes(ctx, file, content) {
33
+ const matches = [...content.matchAll(PATTERNS.route)];
34
+ for (const match of matches) {
35
+ const method = match[1]?.toUpperCase() ?? "GET";
36
+ const path = match[2] ?? "/";
37
+ const index = match.index ?? 0;
38
+ const lineNumber = content.slice(0, index).split("\n").length;
39
+ const afterMatch = content.slice(index, index + 500);
40
+ const hasZodValidator = PATTERNS.zodValidator.test(afterMatch);
41
+ const endpoint = {
42
+ id: this.generateEndpointId(method, path),
43
+ method,
44
+ path,
45
+ kind: this.methodToOpKind(method),
46
+ handlerName: "handler",
47
+ source: this.createLocation(file, lineNumber, lineNumber + 5),
48
+ confidence: this.createConfidence(hasZodValidator ? "high" : "medium", hasZodValidator ? "explicit-schema" : "decorator-hints")
49
+ };
50
+ this.addEndpoint(ctx, endpoint);
51
+ }
52
+ }
53
+ };
54
+
55
+ //#endregion
56
+ export { HonoExtractor };
57
+ //# sourceMappingURL=extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor.js","names":[],"sources":["../../../src/extractors/hono/extractor.ts"],"sourcesContent":["/**\n * Hono extractor.\n *\n * Extracts contract candidates from Hono applications by parsing:\n * - app.get(), app.post(), etc. route handlers\n * - Zod validator middleware\n */\n\nimport { BaseExtractor, type ExtractionContext } from '../base';\nimport type { EndpointCandidate, HttpMethod } from '../../types';\n\nconst PATTERNS = {\n route:\n /(?:app|hono)\\.(get|post|put|patch|delete|all)\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/gi,\n zodValidator: /zValidator\\s*\\(\\s*['\"`](\\w+)['\"`]\\s*,\\s*(\\w+)/g,\n};\n\nexport class HonoExtractor extends BaseExtractor {\n id = 'hono';\n name = 'Hono Extractor';\n frameworks = ['hono'];\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 await this.extractRoutes(ctx, file, content);\n }\n }\n\n private async extractRoutes(\n ctx: ExtractionContext,\n file: string,\n content: string\n ): Promise<void> {\n const matches = [...content.matchAll(PATTERNS.route)];\n\n for (const match of matches) {\n const method = (match[1]?.toUpperCase() ?? 'GET') as HttpMethod;\n const path = match[2] ?? '/';\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 hasZodValidator = PATTERNS.zodValidator.test(afterMatch);\n\n const endpoint: EndpointCandidate = {\n id: this.generateEndpointId(method, path),\n method,\n path,\n kind: this.methodToOpKind(method),\n handlerName: 'handler',\n source: this.createLocation(file, lineNumber, lineNumber + 5),\n confidence: this.createConfidence(\n hasZodValidator ? 'high' : 'medium',\n hasZodValidator ? 'explicit-schema' : 'decorator-hints'\n ),\n };\n\n this.addEndpoint(ctx, endpoint);\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAWA,MAAM,WAAW;CACf,OACE;CACF,cAAc;CACf;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,SAAM,KAAK,cAAc,KAAK,MAAM,QAAQ;;;CAIhD,MAAc,cACZ,KACA,MACA,SACe;EACf,MAAM,UAAU,CAAC,GAAG,QAAQ,SAAS,SAAS,MAAM,CAAC;AAErD,OAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,SAAU,MAAM,IAAI,aAAa,IAAI;GAC3C,MAAM,OAAO,MAAM,MAAM;GACzB,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,kBAAkB,SAAS,aAAa,KAAK,WAAW;GAE9D,MAAM,WAA8B;IAClC,IAAI,KAAK,mBAAmB,QAAQ,KAAK;IACzC;IACA;IACA,MAAM,KAAK,eAAe,OAAO;IACjC,aAAa;IACb,QAAQ,KAAK,eAAe,MAAM,YAAY,aAAa,EAAE;IAC7D,YAAY,KAAK,iBACf,kBAAkB,SAAS,UAC3B,kBAAkB,oBAAoB,kBACvC;IACF;AAED,QAAK,YAAY,KAAK,SAAS"}
@@ -0,0 +1,21 @@
1
+ import { BaseExtractor, ExtractionContext, ExtractorFsAdapter } from "./base.js";
2
+ import { NestJsExtractor } from "./nestjs/extractor.js";
3
+ import { ExpressExtractor } from "./express/extractor.js";
4
+ import { FastifyExtractor } from "./fastify/extractor.js";
5
+ import { HonoExtractor } from "./hono/extractor.js";
6
+ import { ElysiaExtractor } from "./elysia/extractor.js";
7
+ import { TrpcExtractor } from "./trpc/extractor.js";
8
+ import { NextApiExtractor } from "./next-api/extractor.js";
9
+ import { ZodSchemaExtractor } from "./zod/extractor.js";
10
+
11
+ //#region src/extractors/index.d.ts
12
+ declare namespace index_d_exports {
13
+ export { BaseExtractor, ElysiaExtractor, ExpressExtractor, ExtractionContext, ExtractorFsAdapter, FastifyExtractor, HonoExtractor, NestJsExtractor, NextApiExtractor, TrpcExtractor, ZodSchemaExtractor, registerAllExtractors };
14
+ }
15
+ /**
16
+ * Register all built-in extractors with the global registry.
17
+ */
18
+ declare function registerAllExtractors(): void;
19
+ //#endregion
20
+ export { BaseExtractor, ElysiaExtractor, ExpressExtractor, type ExtractionContext, type ExtractorFsAdapter, FastifyExtractor, HonoExtractor, NestJsExtractor, NextApiExtractor, TrpcExtractor, ZodSchemaExtractor, index_d_exports, registerAllExtractors };
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/extractors/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;iBAoCgB,qBAAA,CAAA"}
@@ -0,0 +1,42 @@
1
+ import { __exportAll } from "../_virtual/rolldown_runtime.js";
2
+ import { extractorRegistry } from "../registry.js";
3
+ import { BaseExtractor } from "./base.js";
4
+ import { NestJsExtractor } from "./nestjs/extractor.js";
5
+ import { ExpressExtractor } from "./express/extractor.js";
6
+ import { FastifyExtractor } from "./fastify/extractor.js";
7
+ import { HonoExtractor } from "./hono/extractor.js";
8
+ import { ElysiaExtractor } from "./elysia/extractor.js";
9
+ import { TrpcExtractor } from "./trpc/extractor.js";
10
+ import { NextApiExtractor } from "./next-api/extractor.js";
11
+ import { ZodSchemaExtractor } from "./zod/extractor.js";
12
+
13
+ //#region src/extractors/index.ts
14
+ var extractors_exports = /* @__PURE__ */ __exportAll({
15
+ BaseExtractor: () => BaseExtractor,
16
+ ElysiaExtractor: () => ElysiaExtractor,
17
+ ExpressExtractor: () => ExpressExtractor,
18
+ FastifyExtractor: () => FastifyExtractor,
19
+ HonoExtractor: () => HonoExtractor,
20
+ NestJsExtractor: () => NestJsExtractor,
21
+ NextApiExtractor: () => NextApiExtractor,
22
+ TrpcExtractor: () => TrpcExtractor,
23
+ ZodSchemaExtractor: () => ZodSchemaExtractor,
24
+ registerAllExtractors: () => registerAllExtractors
25
+ });
26
+ /**
27
+ * Register all built-in extractors with the global registry.
28
+ */
29
+ function registerAllExtractors() {
30
+ extractorRegistry.register(new NestJsExtractor());
31
+ extractorRegistry.register(new ExpressExtractor());
32
+ extractorRegistry.register(new FastifyExtractor());
33
+ extractorRegistry.register(new HonoExtractor());
34
+ extractorRegistry.register(new ElysiaExtractor());
35
+ extractorRegistry.register(new TrpcExtractor());
36
+ extractorRegistry.register(new NextApiExtractor());
37
+ extractorRegistry.register(new ZodSchemaExtractor());
38
+ }
39
+
40
+ //#endregion
41
+ export { BaseExtractor, ElysiaExtractor, ExpressExtractor, FastifyExtractor, HonoExtractor, NestJsExtractor, NextApiExtractor, TrpcExtractor, ZodSchemaExtractor, extractors_exports, registerAllExtractors };
42
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/extractors/index.ts"],"sourcesContent":["/**\n * Source extractors module.\n *\n * Exports all available framework extractors.\n */\n\n// Base extractor class\nexport { BaseExtractor } from './base';\nexport type { ExtractorFsAdapter, ExtractionContext } from './base';\n\n// Framework-specific extractors\nexport { NestJsExtractor } from './nestjs/extractor';\nexport { ExpressExtractor } from './express/extractor';\nexport { FastifyExtractor } from './fastify/extractor';\nexport { HonoExtractor } from './hono/extractor';\nexport { ElysiaExtractor } from './elysia/extractor';\nexport { TrpcExtractor } from './trpc/extractor';\nexport { NextApiExtractor } from './next-api/extractor';\n\n// Schema extractors\nexport { ZodSchemaExtractor } from './zod/extractor';\n\n// Registration helper\nimport { extractorRegistry } from '../registry';\nimport { NestJsExtractor } from './nestjs/extractor';\nimport { ExpressExtractor } from './express/extractor';\nimport { FastifyExtractor } from './fastify/extractor';\nimport { HonoExtractor } from './hono/extractor';\nimport { ElysiaExtractor } from './elysia/extractor';\nimport { TrpcExtractor } from './trpc/extractor';\nimport { NextApiExtractor } from './next-api/extractor';\nimport { ZodSchemaExtractor } from './zod/extractor';\n\n/**\n * Register all built-in extractors with the global registry.\n */\nexport function registerAllExtractors(): void {\n extractorRegistry.register(new NestJsExtractor());\n extractorRegistry.register(new ExpressExtractor());\n extractorRegistry.register(new FastifyExtractor());\n extractorRegistry.register(new HonoExtractor());\n extractorRegistry.register(new ElysiaExtractor());\n extractorRegistry.register(new TrpcExtractor());\n extractorRegistry.register(new NextApiExtractor());\n extractorRegistry.register(new ZodSchemaExtractor());\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,wBAA8B;AAC5C,mBAAkB,SAAS,IAAI,iBAAiB,CAAC;AACjD,mBAAkB,SAAS,IAAI,kBAAkB,CAAC;AAClD,mBAAkB,SAAS,IAAI,kBAAkB,CAAC;AAClD,mBAAkB,SAAS,IAAI,eAAe,CAAC;AAC/C,mBAAkB,SAAS,IAAI,iBAAiB,CAAC;AACjD,mBAAkB,SAAS,IAAI,eAAe,CAAC;AAC/C,mBAAkB,SAAS,IAAI,kBAAkB,CAAC;AAClD,mBAAkB,SAAS,IAAI,oBAAoB,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { BaseExtractor, ExtractionContext } from "../base.js";
2
+
3
+ //#region src/extractors/nestjs/extractor.d.ts
4
+
5
+ /**
6
+ * NestJS framework extractor.
7
+ */
8
+ declare class NestJsExtractor extends BaseExtractor {
9
+ id: string;
10
+ name: string;
11
+ frameworks: string[];
12
+ priority: number;
13
+ protected doExtract(ctx: ExtractionContext): Promise<void>;
14
+ /**
15
+ * Extract controllers and their routes from a file.
16
+ */
17
+ private extractControllers;
18
+ /**
19
+ * Extract DTO classes from a file.
20
+ */
21
+ private extractDtos;
22
+ /**
23
+ * Normalize a path (remove double slashes, ensure leading slash).
24
+ */
25
+ private normalizePath;
26
+ }
27
+ //#endregion
28
+ export { NestJsExtractor };
29
+ //# sourceMappingURL=extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor.d.ts","names":[],"sources":["../../../src/extractors/nestjs/extractor.ts"],"sourcesContent":[],"mappings":";;;;;;;cAmCa,eAAA,SAAwB,aAAA;;;;;2BAMJ,oBAAoB"}
@@ -0,0 +1,118 @@
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
@@ -0,0 +1 @@
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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"}
@@ -0,0 +1,16 @@
1
+ import { BaseExtractor, ExtractionContext } from "../base.js";
2
+
3
+ //#region src/extractors/next-api/extractor.d.ts
4
+
5
+ declare class NextApiExtractor extends BaseExtractor {
6
+ id: string;
7
+ name: string;
8
+ frameworks: string[];
9
+ priority: number;
10
+ protected doExtract(ctx: ExtractionContext): Promise<void>;
11
+ private extractAppRoutes;
12
+ private extractPagesRoutes;
13
+ }
14
+ //#endregion
15
+ export { NextApiExtractor };
16
+ //# sourceMappingURL=extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor.d.ts","names":[],"sources":["../../../src/extractors/next-api/extractor.ts"],"sourcesContent":[],"mappings":";;;;cAkBa,gBAAA,SAAyB,aAAA;;;;;2BAML,oBAAoB"}