@hyperfixi/server-bridge 2.0.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 (77) hide show
  1. package/LICENSE +20 -0
  2. package/dist/cli/config.d.ts +5 -0
  3. package/dist/cli/diff-command.d.ts +9 -0
  4. package/dist/cli/generate-command.d.ts +12 -0
  5. package/dist/cli/index.d.ts +2 -0
  6. package/dist/cli/scan-command.d.ts +9 -0
  7. package/dist/cli/watch-command.d.ts +30 -0
  8. package/dist/cli.cjs +1853 -0
  9. package/dist/cli.cjs.map +1 -0
  10. package/dist/conventions/convention-engine.d.ts +29 -0
  11. package/dist/conventions/url-patterns.d.ts +37 -0
  12. package/dist/generate.d.ts +21 -0
  13. package/dist/generators/django-generator.d.ts +10 -0
  14. package/dist/generators/express-generator.d.ts +9 -0
  15. package/dist/generators/fastapi-generator.d.ts +10 -0
  16. package/dist/generators/hono-generator.d.ts +9 -0
  17. package/dist/generators/index.d.ts +6 -0
  18. package/dist/generators/manifest.d.ts +43 -0
  19. package/dist/generators/openapi-generator.d.ts +14 -0
  20. package/dist/generators/template-helpers.d.ts +24 -0
  21. package/dist/generators/types.d.ts +11 -0
  22. package/dist/index.cjs +1426 -0
  23. package/dist/index.cjs.map +1 -0
  24. package/dist/index.d.ts +19 -0
  25. package/dist/index.js +1376 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/scanner/conflict-detector.d.ts +10 -0
  28. package/dist/scanner/form-scanner.d.ts +12 -0
  29. package/dist/scanner/htmx-extractor.d.ts +9 -0
  30. package/dist/scanner/hyperscript-extractor.d.ts +9 -0
  31. package/dist/scanner/route-scanner.d.ts +21 -0
  32. package/dist/types.d.ts +123 -0
  33. package/package.json +64 -0
  34. package/src/__tests__/cli.test.ts +30 -0
  35. package/src/__tests__/conflict-detector.test.ts +126 -0
  36. package/src/__tests__/convention-engine.test.ts +58 -0
  37. package/src/__tests__/diff-command.test.ts +69 -0
  38. package/src/__tests__/django-generator.test.ts +129 -0
  39. package/src/__tests__/express-generator.test.ts +105 -0
  40. package/src/__tests__/fastapi-generator.test.ts +125 -0
  41. package/src/__tests__/form-scanner.test.ts +91 -0
  42. package/src/__tests__/generate.test.ts +89 -0
  43. package/src/__tests__/hono-generator.test.ts +90 -0
  44. package/src/__tests__/htmx-extractor.test.ts +84 -0
  45. package/src/__tests__/hyperscript-extractor.test.ts +89 -0
  46. package/src/__tests__/integration.test.ts +102 -0
  47. package/src/__tests__/manifest.test.ts +133 -0
  48. package/src/__tests__/openapi-generator.test.ts +116 -0
  49. package/src/__tests__/query-params.test.ts +101 -0
  50. package/src/__tests__/route-scanner.test.ts +47 -0
  51. package/src/__tests__/url-patterns.test.ts +74 -0
  52. package/src/__tests__/watch-command.test.ts +141 -0
  53. package/src/cli/config.ts +35 -0
  54. package/src/cli/diff-command.ts +96 -0
  55. package/src/cli/generate-command.ts +108 -0
  56. package/src/cli/index.ts +143 -0
  57. package/src/cli/scan-command.ts +72 -0
  58. package/src/cli/watch-command.ts +242 -0
  59. package/src/conventions/convention-engine.ts +68 -0
  60. package/src/conventions/url-patterns.ts +123 -0
  61. package/src/generate.ts +44 -0
  62. package/src/generators/django-generator.ts +210 -0
  63. package/src/generators/express-generator.ts +137 -0
  64. package/src/generators/fastapi-generator.ts +248 -0
  65. package/src/generators/hono-generator.ts +137 -0
  66. package/src/generators/index.ts +25 -0
  67. package/src/generators/manifest.ts +164 -0
  68. package/src/generators/openapi-generator.ts +259 -0
  69. package/src/generators/template-helpers.ts +77 -0
  70. package/src/generators/types.ts +12 -0
  71. package/src/index.ts +42 -0
  72. package/src/scanner/conflict-detector.ts +92 -0
  73. package/src/scanner/form-scanner.ts +126 -0
  74. package/src/scanner/htmx-extractor.ts +174 -0
  75. package/src/scanner/hyperscript-extractor.ts +184 -0
  76. package/src/scanner/route-scanner.ts +130 -0
  77. package/src/types.ts +151 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/conventions/url-patterns.ts","../src/conventions/convention-engine.ts","../src/scanner/hyperscript-extractor.ts","../src/scanner/htmx-extractor.ts","../src/scanner/form-scanner.ts","../src/scanner/route-scanner.ts","../src/scanner/conflict-detector.ts","../src/generators/template-helpers.ts","../src/generators/express-generator.ts","../src/generators/hono-generator.ts","../src/generators/openapi-generator.ts","../src/generators/django-generator.ts","../src/generators/fastapi-generator.ts","../src/generators/index.ts","../src/generators/manifest.ts","../src/generate.ts"],"sourcesContent":["import type { HttpMethod, QueryParam } from '../types.js';\n\n/**\n * Extract path parameter names from a URL pattern.\n * Handles both `:param` and `{param}` styles.\n *\n * @example\n * extractPathParams('/api/users/:id') // ['id']\n * extractPathParams('/api/users/{id}/orders/{orderId}') // ['id', 'orderId']\n */\nexport function extractPathParams(url: string): string[] {\n const params: string[] = [];\n // Match :param style\n for (const match of url.matchAll(/:(\\w+)/g)) {\n params.push(match[1]);\n }\n // Match {param} style\n for (const match of url.matchAll(/\\{(\\w+)\\}/g)) {\n if (!params.includes(match[1])) {\n params.push(match[1]);\n }\n }\n return params;\n}\n\n/**\n * Normalize a URL path:\n * - Convert `{param}` to `:param`\n * - Strip trailing slashes\n * - Ensure leading slash\n * - Strip query strings\n * - Strip fragments\n */\nexport function normalizeUrl(url: string): string {\n // Strip query string and fragment\n let normalized = url.split('?')[0].split('#')[0];\n\n // Convert {param} to :param\n normalized = normalized.replace(/\\{(\\w+)\\}/g, ':$1');\n\n // Ensure leading slash\n if (!normalized.startsWith('/')) {\n normalized = '/' + normalized;\n }\n\n // Strip trailing slash (but keep root /)\n if (normalized.length > 1 && normalized.endsWith('/')) {\n normalized = normalized.slice(0, -1);\n }\n\n return normalized;\n}\n\n/**\n * Convert a URL path + method into a handler function name.\n *\n * @example\n * urlToHandlerName('/api/users', 'GET') // 'getApiUsers'\n * urlToHandlerName('/api/users/:id', 'POST') // 'postApiUsersById'\n */\nexport function urlToHandlerName(url: string, method: HttpMethod): string {\n const prefix = method.toLowerCase();\n\n // Remove leading slash, split by / and :\n const segments = url\n .replace(/^\\//, '')\n .split('/')\n .map(segment => {\n if (segment.startsWith(':')) {\n // :id -> ById, :userId -> ByUserId\n const param = segment.slice(1);\n return 'By' + capitalize(param);\n }\n // api -> Api, user-list -> UserList\n return segment.split(/[-_]/).map(capitalize).join('');\n });\n\n return prefix + segments.join('');\n}\n\n/**\n * Extract query parameter names from a URL string.\n * Must be called BEFORE normalizeUrl (which strips query strings).\n *\n * @example\n * extractQueryParams('/api/search?q=hello&limit=10')\n * // [{ name: 'q', type: 'string', required: true },\n * // { name: 'limit', type: 'string', required: true }]\n */\nexport function extractQueryParams(url: string): QueryParam[] {\n const queryIndex = url.indexOf('?');\n if (queryIndex === -1) return [];\n\n const queryString = url.slice(queryIndex + 1).split('#')[0];\n if (!queryString) return [];\n\n const params: QueryParam[] = [];\n const seen = new Set<string>();\n\n for (const pair of queryString.split('&')) {\n const eqIndex = pair.indexOf('=');\n const rawName = eqIndex === -1 ? pair : pair.slice(0, eqIndex);\n if (!rawName) continue;\n\n let name: string;\n try {\n name = decodeURIComponent(rawName);\n } catch {\n name = rawName;\n }\n if (seen.has(name)) continue;\n seen.add(name);\n\n params.push({ name, type: 'string', required: true });\n }\n\n return params;\n}\n\nfunction capitalize(s: string): string {\n if (!s) return '';\n return s[0].toUpperCase() + s.slice(1);\n}\n","import type { HttpMethod } from '../types.js';\nimport { urlToHandlerName } from './url-patterns.js';\n\nexport interface ConventionContext {\n /** HTML element tag (e.g., 'form', 'button') */\n elementTag?: string;\n /** Explicit response type from hyperscript (e.g., 'json', 'html') */\n fetchResponseType?: string;\n /** Explicit method from source (e.g., from hx-post, fx-method) */\n explicitMethod?: string;\n}\n\nexport interface ConventionResult {\n method: HttpMethod;\n responseFormat: 'json' | 'text' | 'html';\n handlerName: string;\n notes: string[];\n}\n\n/**\n * Infer HTTP method, response format, and handler name from URL and context.\n *\n * Priority:\n * 1. Explicit method always wins (hx-post, fx-method, fetch via POST)\n * 2. Form context implies POST\n * 3. Default: GET\n *\n * Response format:\n * 1. Explicit format from hyperscript (as json, as html)\n * 2. URL ending in .html → html\n * 3. Default: json\n */\nexport function inferConventions(url: string, context: ConventionContext = {}): ConventionResult {\n const notes: string[] = [];\n\n // --- Method ---\n let method: HttpMethod;\n if (context.explicitMethod) {\n method = context.explicitMethod.toUpperCase() as HttpMethod;\n } else if (context.elementTag === 'form') {\n method = 'POST';\n notes.push('Inferred POST from <form> context');\n } else {\n method = 'GET';\n }\n\n // --- Response Format ---\n let responseFormat: 'json' | 'text' | 'html';\n if (context.fetchResponseType) {\n const fmt = context.fetchResponseType.toLowerCase();\n if (fmt === 'json' || fmt === 'html' || fmt === 'text') {\n responseFormat = fmt;\n } else {\n responseFormat = 'json';\n notes.push(`Unknown response type \"${context.fetchResponseType}\", defaulting to json`);\n }\n } else if (url.endsWith('.html') || url.includes('/pages/') || url.includes('/partials/')) {\n responseFormat = 'html';\n notes.push('Inferred html response from URL pattern');\n } else {\n responseFormat = 'json';\n }\n\n // --- Handler Name ---\n const handlerName = urlToHandlerName(url, method);\n\n return { method, responseFormat, handlerName, notes };\n}\n","import type { RouteDescriptor, RouteSource } from '../types.js';\nimport {\n extractPathParams,\n extractQueryParams,\n normalizeUrl,\n} from '../conventions/url-patterns.js';\nimport { inferConventions } from '../conventions/convention-engine.js';\n\n/**\n * Regex patterns for finding hyperscript with fetch commands.\n * Matches _=\"...\" attributes in various quote styles.\n */\nconst ATTR_PATTERNS = [\n /_\\s*=\\s*\"([^\"]+)\"/g,\n /_\\s*=\\s*'([^']+)'/g,\n /_\\s*=\\s*`([^`]+)`/g,\n /_=\\{`([^`]+)`\\}/g, // JSX template literal\n /_=\\{['\"]([^'\"]+)['\"]\\}/g, // JSX string\n];\n\nconst SCRIPT_PATTERN = /<script[^>]*type=[\"']?text\\/hyperscript[\"']?[^>]*>([\\s\\S]*?)<\\/script>/gi;\n\n/**\n * Regex for fetch commands within hyperscript code.\n * Captures: url, optional response type (as json/html/text), optional method (via GET/POST)\n */\nconst FETCH_COMMAND = /\\bfetch\\s+['\"]?([^\\s'\"]+)['\"]?(?:\\s+as\\s+(\\w+))?(?:\\s+via\\s+(\\w+))?/gi;\n\n/**\n * Regex for \"send form to URL\" pattern (implies POST).\n */\nconst SEND_FORM = /\\bsend\\s+(?:the\\s+)?form\\s+to\\s+['\"]?([^\\s'\"]+)['\"]?(?:\\s+as\\s+(\\w+))?/gi;\n\ninterface ExtractionOptions {\n file: string;\n}\n\n/**\n * Extract route descriptors from hyperscript attributes in file content.\n */\nexport function extractHyperscriptRoutes(\n content: string,\n options: ExtractionOptions\n): RouteDescriptor[] {\n const routes: RouteDescriptor[] = [];\n const seen = new Set<string>();\n\n // Collect all hyperscript code regions with their line numbers\n const regions = extractHyperscriptRegions(content);\n\n for (const region of regions) {\n // Extract fetch commands\n for (const match of region.code.matchAll(FETCH_COMMAND)) {\n const rawUrl = match[1];\n const responseType = match[2]; // json, html, text\n const method = match[3]; // GET, POST, etc.\n\n addRoute(routes, seen, rawUrl, {\n file: options.file,\n line: region.line,\n kind: 'fetch',\n raw: match[0],\n explicitMethod: method,\n fetchResponseType: responseType,\n });\n }\n\n // Extract \"send form to\" commands\n for (const match of region.code.matchAll(SEND_FORM)) {\n const rawUrl = match[1];\n const responseType = match[2];\n\n addRoute(routes, seen, rawUrl, {\n file: options.file,\n line: region.line,\n kind: 'fetch',\n raw: match[0],\n explicitMethod: 'POST',\n fetchResponseType: responseType,\n });\n }\n }\n\n return routes;\n}\n\ninterface RegionInfo {\n code: string;\n line: number;\n}\n\nfunction extractHyperscriptRegions(content: string): RegionInfo[] {\n const regions: RegionInfo[] = [];\n const lines = content.split('\\n');\n\n // Search for _=\"...\" attributes\n for (const pattern of ATTR_PATTERNS) {\n // Reset the regex\n pattern.lastIndex = 0;\n let match;\n while ((match = pattern.exec(content)) !== null) {\n const line = lineNumberAt(content, match.index);\n regions.push({ code: match[1], line });\n }\n }\n\n // Search for <script type=\"text/hyperscript\"> blocks\n SCRIPT_PATTERN.lastIndex = 0;\n let match;\n while ((match = SCRIPT_PATTERN.exec(content)) !== null) {\n const line = lineNumberAt(content, match.index);\n regions.push({ code: match[1], line });\n }\n\n return regions;\n}\n\nfunction lineNumberAt(content: string, index: number): number {\n let line = 1;\n for (let i = 0; i < index && i < content.length; i++) {\n if (content[i] === '\\n') line++;\n }\n return line;\n}\n\ninterface AddRouteOptions {\n file: string;\n line: number;\n kind: RouteSource['kind'];\n raw: string;\n explicitMethod?: string;\n fetchResponseType?: string;\n}\n\nfunction addRoute(\n routes: RouteDescriptor[],\n seen: Set<string>,\n rawUrl: string,\n options: AddRouteOptions\n): void {\n // Skip external URLs\n if (rawUrl.startsWith('http://') || rawUrl.startsWith('https://')) {\n return;\n }\n\n // Skip template-only URLs (e.g., {{baseUrl}})\n if (rawUrl.startsWith('{{') || rawUrl.startsWith('${')) {\n return;\n }\n\n // Skip non-URL strings (e.g., CSS selectors, keywords, bare words ending in colon)\n if (!rawUrl.startsWith('/') && !rawUrl.includes('/')) {\n return;\n }\n\n // Extract query params BEFORE normalization (which strips them)\n const queryParams = extractQueryParams(rawUrl);\n const path = normalizeUrl(rawUrl);\n\n const conventions = inferConventions(path, {\n explicitMethod: options.explicitMethod,\n fetchResponseType: options.fetchResponseType,\n });\n\n const key = `${conventions.method}:${path}`;\n if (seen.has(key)) return;\n seen.add(key);\n\n routes.push({\n path,\n method: conventions.method,\n responseFormat: conventions.responseFormat,\n source: {\n file: options.file,\n line: options.line,\n kind: options.kind,\n raw: options.raw,\n },\n pathParams: extractPathParams(path),\n ...(queryParams.length > 0 ? { queryParams } : {}),\n handlerName: conventions.handlerName,\n notes: conventions.notes,\n });\n}\n","import type { RouteDescriptor, HttpMethod, RouteSource } from '../types.js';\nimport {\n extractPathParams,\n extractQueryParams,\n normalizeUrl,\n} from '../conventions/url-patterns.js';\nimport { inferConventions } from '../conventions/convention-engine.js';\n\n/**\n * Regex patterns forked from packages/vite-plugin/src/scanner.ts (lines 12-21)\n * with URL capture groups added for route extraction.\n */\nconst HTMX_REQUEST_PATTERN =\n /\\b(hx-get|hx-post|hx-put|hx-patch|hx-delete)\\s*=\\s*[\"']([^\"']+)[\"']/gi;\n\nconst FIXI_ACTION_PATTERN = /\\bfx-action\\s*=\\s*[\"']([^\"']+)[\"']/gi;\nconst FIXI_METHOD_PATTERN = /\\bfx-method\\s*=\\s*[\"'](GET|POST|PUT|PATCH|DELETE)[\"']/gi;\n\nconst HTMX_TARGET_PATTERN = /\\b(?:hx-target|fx-target)\\s*=\\s*[\"']([^\"']+)[\"']/gi;\nconst HTMX_SWAP_PATTERN = /\\b(?:hx-swap|fx-swap)\\s*=\\s*[\"']([^\"']+)[\"']/gi;\n\n/** Map hx-{method} attribute names to HTTP methods */\nconst HTMX_METHOD_MAP: Record<string, HttpMethod> = {\n 'hx-get': 'GET',\n 'hx-post': 'POST',\n 'hx-put': 'PUT',\n 'hx-patch': 'PATCH',\n 'hx-delete': 'DELETE',\n};\n\ninterface ExtractionOptions {\n file: string;\n}\n\n/**\n * Extract route descriptors from htmx (hx-*) and fixi (fx-*) attributes.\n */\nexport function extractHtmxRoutes(content: string, options: ExtractionOptions): RouteDescriptor[] {\n const routes: RouteDescriptor[] = [];\n const seen = new Set<string>();\n\n // --- htmx request attributes: hx-get, hx-post, etc. ---\n HTMX_REQUEST_PATTERN.lastIndex = 0;\n let match;\n while ((match = HTMX_REQUEST_PATTERN.exec(content)) !== null) {\n const attrName = match[1].toLowerCase();\n const rawUrl = match[2];\n const method = HTMX_METHOD_MAP[attrName];\n if (!method) continue;\n\n addRoute(routes, seen, rawUrl, {\n file: options.file,\n line: lineNumberAt(content, match.index),\n kind: 'hx-attr',\n raw: `${match[1]}=\"${match[2]}\"`,\n explicitMethod: method,\n // htmx default response is html (server-rendered partials)\n fetchResponseType: 'html',\n });\n }\n\n // --- fixi action attributes ---\n // Collect fx-method if present (for context)\n const fxMethods = collectFxMethods(content);\n\n FIXI_ACTION_PATTERN.lastIndex = 0;\n while ((match = FIXI_ACTION_PATTERN.exec(content)) !== null) {\n const rawUrl = match[1];\n const line = lineNumberAt(content, match.index);\n\n // Try to find a fx-method near this fx-action (within ~200 chars)\n const nearbyMethod = findNearbyFxMethod(content, match.index, fxMethods);\n\n addRoute(routes, seen, rawUrl, {\n file: options.file,\n line,\n kind: 'fx-attr',\n raw: `fx-action=\"${match[1]}\"`,\n explicitMethod: nearbyMethod,\n });\n }\n\n return routes;\n}\n\nfunction lineNumberAt(content: string, index: number): number {\n let line = 1;\n for (let i = 0; i < index && i < content.length; i++) {\n if (content[i] === '\\n') line++;\n }\n return line;\n}\n\ninterface FxMethodMatch {\n method: string;\n index: number;\n}\n\nfunction collectFxMethods(content: string): FxMethodMatch[] {\n const methods: FxMethodMatch[] = [];\n FIXI_METHOD_PATTERN.lastIndex = 0;\n let match;\n while ((match = FIXI_METHOD_PATTERN.exec(content)) !== null) {\n methods.push({ method: match[1], index: match.index });\n }\n return methods;\n}\n\n/**\n * Find an fx-method attribute near an fx-action attribute.\n * \"Near\" means within the same HTML element (~300 chars).\n */\nfunction findNearbyFxMethod(\n content: string,\n actionIndex: number,\n fxMethods: FxMethodMatch[]\n): string | undefined {\n for (const fm of fxMethods) {\n if (Math.abs(fm.index - actionIndex) < 300) {\n return fm.method;\n }\n }\n return undefined;\n}\n\ninterface AddRouteOptions {\n file: string;\n line: number;\n kind: RouteSource['kind'];\n raw: string;\n explicitMethod?: string;\n fetchResponseType?: string;\n}\n\nfunction addRoute(\n routes: RouteDescriptor[],\n seen: Set<string>,\n rawUrl: string,\n options: AddRouteOptions\n): void {\n // Skip external URLs\n if (rawUrl.startsWith('http://') || rawUrl.startsWith('https://')) {\n return;\n }\n\n // Extract query params BEFORE normalization (which strips them)\n const queryParams = extractQueryParams(rawUrl);\n const path = normalizeUrl(rawUrl);\n\n const conventions = inferConventions(path, {\n explicitMethod: options.explicitMethod,\n fetchResponseType: options.fetchResponseType,\n });\n\n const key = `${conventions.method}:${path}`;\n if (seen.has(key)) return;\n seen.add(key);\n\n routes.push({\n path,\n method: conventions.method,\n responseFormat: conventions.responseFormat,\n source: {\n file: options.file,\n line: options.line,\n kind: options.kind,\n raw: options.raw,\n },\n pathParams: extractPathParams(path),\n ...(queryParams.length > 0 ? { queryParams } : {}),\n handlerName: conventions.handlerName,\n notes: conventions.notes,\n });\n}\n","import type { RequestBodyField } from '../types.js';\n\n/**\n * Regex to match <form> blocks and capture inner content.\n * Uses non-greedy matching to handle multiple forms.\n */\nconst FORM_BLOCK = /<form\\b[^>]*>([\\s\\S]*?)<\\/form>/gi;\n\n/**\n * Regex to match input-like elements with a name attribute.\n * Captures: tag name, full attributes string.\n */\nconst NAMED_FIELD = /<(input|select|textarea)\\b([^>]*)\\bname\\s*=\\s*[\"']([^\"']+)[\"']([^>]*)>/gi;\n\n/** Regex for type attribute */\nconst TYPE_ATTR = /\\btype\\s*=\\s*[\"']([^\"']+)[\"']/i;\n\n/** Regex for required attribute */\nconst REQUIRED_ATTR = /\\brequired\\b/i;\n\n/** Map HTML input types to our field types */\nconst TYPE_MAP: Record<string, RequestBodyField['type']> = {\n text: 'string',\n email: 'string',\n password: 'string',\n tel: 'string',\n url: 'string',\n search: 'string',\n hidden: 'string',\n color: 'string',\n date: 'string',\n 'datetime-local': 'string',\n month: 'string',\n week: 'string',\n time: 'string',\n number: 'number',\n range: 'number',\n checkbox: 'boolean',\n file: 'file',\n};\n\n/**\n * Extract form fields from a <form> block's inner HTML.\n */\nexport function extractFormFields(formInnerHtml: string): RequestBodyField[] {\n const fields: RequestBodyField[] = [];\n const seen = new Set<string>();\n\n NAMED_FIELD.lastIndex = 0;\n let match;\n while ((match = NAMED_FIELD.exec(formInnerHtml)) !== null) {\n const tag = match[1].toLowerCase();\n const attrsBefore = match[2];\n const name = match[3];\n const attrsAfter = match[4];\n const allAttrs = attrsBefore + ' ' + attrsAfter;\n\n if (seen.has(name)) continue;\n seen.add(name);\n\n let type: RequestBodyField['type'] = 'string';\n if (tag === 'select' || tag === 'textarea') {\n type = 'string';\n } else {\n const typeMatch = allAttrs.match(TYPE_ATTR);\n const inputType = typeMatch ? typeMatch[1].toLowerCase() : 'text';\n type = TYPE_MAP[inputType] ?? 'unknown';\n }\n\n const required = REQUIRED_ATTR.test(allAttrs);\n\n fields.push({ name, type, required });\n }\n\n return fields;\n}\n\n/**\n * Find all <form> blocks in HTML content that contain route-implying attributes\n * (hx-post, hx-put, hx-patch, fx-action) and extract their fields.\n *\n * Returns a map from URL path to field list.\n */\nexport function extractFormBodies(content: string): Map<string, RequestBodyField[]> {\n const result = new Map<string, RequestBodyField[]>();\n\n FORM_BLOCK.lastIndex = 0;\n let match;\n while ((match = FORM_BLOCK.exec(content)) !== null) {\n const formTag = content.slice(\n match.index,\n match.index + content.slice(match.index).indexOf('>') + 1\n );\n const formBody = match[1];\n\n // Check for route-implying attributes in the form tag\n const url = extractFormUrl(formTag);\n if (!url) continue;\n\n const fields = extractFormFields(formBody);\n if (fields.length > 0) {\n result.set(url, fields);\n }\n }\n\n return result;\n}\n\n/**\n * Extract URL from a form tag's route-implying attributes.\n */\nfunction extractFormUrl(formTag: string): string | undefined {\n // hx-post, hx-put, hx-patch\n const hxMatch = formTag.match(/\\bhx-(?:post|put|patch)\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (hxMatch) return hxMatch[1];\n\n // fx-action\n const fxMatch = formTag.match(/\\bfx-action\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (fxMatch) return fxMatch[1];\n\n // hyperscript with send form to\n const hsMatch = formTag.match(/\\bsend\\s+(?:the\\s+)?form\\s+to\\s+['\"]?([^\\s'\"]+)['\"]?/i);\n if (hsMatch) return hsMatch[1];\n\n return undefined;\n}\n","import { readFile } from 'node:fs/promises';\nimport { glob } from 'glob';\nimport type { RouteDescriptor, ScanResult, ScanError } from '../types.js';\nimport { extractHyperscriptRoutes } from './hyperscript-extractor.js';\nimport { extractHtmxRoutes } from './htmx-extractor.js';\nimport { extractFormBodies } from './form-scanner.js';\nimport { detectConflicts } from './conflict-detector.js';\nimport { normalizeUrl } from '../conventions/url-patterns.js';\n\n/**\n * Default file patterns to scan.\n */\nconst DEFAULT_INCLUDE = ['**/*.html', '**/*.htm'];\nconst DEFAULT_EXCLUDE = ['**/node_modules/**', '**/dist/**', '**/.git/**'];\n\ninterface ScanOptions {\n include?: string[];\n exclude?: string[];\n /** URL patterns to ignore */\n ignore?: string[];\n}\n\n/**\n * Scan a single file's content for routes.\n */\nexport function scanRoutes(content: string, file: string): RouteDescriptor[] {\n const hsRoutes = extractHyperscriptRoutes(content, { file });\n const htmxRoutes = extractHtmxRoutes(content, { file });\n\n // Merge and deduplicate\n const all = [...hsRoutes, ...htmxRoutes];\n const deduped = deduplicateRoutes(all);\n\n // Enrich with form body data\n const formBodies = extractFormBodies(content);\n for (const route of deduped) {\n if (['POST', 'PUT', 'PATCH'].includes(route.method)) {\n const rawUrl = route.path;\n const fields = formBodies.get(rawUrl);\n if (fields && fields.length > 0) {\n route.requestBody = fields;\n }\n }\n }\n\n return deduped;\n}\n\n/**\n * Scan a directory for all HTML/template files and extract routes.\n */\nexport async function scanDirectory(dir: string, options: ScanOptions = {}): Promise<ScanResult> {\n const include = options.include ?? DEFAULT_INCLUDE;\n const exclude = options.exclude ?? DEFAULT_EXCLUDE;\n\n const files: string[] = [];\n for (const pattern of include) {\n const matches = await glob(pattern, {\n cwd: dir,\n absolute: true,\n ignore: exclude,\n });\n files.push(...matches);\n }\n\n // Deduplicate file paths\n const uniqueFiles = [...new Set(files)];\n\n const allRoutes: RouteDescriptor[] = [];\n const errors: ScanError[] = [];\n\n for (const file of uniqueFiles) {\n try {\n const content = await readFile(file, 'utf-8');\n const routes = scanRoutes(content, file);\n allRoutes.push(...routes);\n } catch (err) {\n errors.push({\n file,\n message: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n // Detect conflicts BEFORE deduplication (needs all occurrences)\n const conflicts = detectConflicts(allRoutes);\n\n // Final deduplication across files\n const deduped = deduplicateRoutes(allRoutes);\n\n // Apply ignore patterns\n const ignored = options.ignore ?? [];\n const filtered = deduped.filter(\n route => !ignored.some(pattern => matchIgnorePattern(route.path, pattern))\n );\n\n return {\n routes: filtered,\n filesScanned: uniqueFiles,\n errors,\n conflicts,\n };\n}\n\n/**\n * Deduplicate routes by method + path.\n * First occurrence wins (preserves source from first detection).\n */\nfunction deduplicateRoutes(routes: RouteDescriptor[]): RouteDescriptor[] {\n const seen = new Map<string, RouteDescriptor>();\n for (const route of routes) {\n const key = `${route.method}:${route.path}`;\n if (!seen.has(key)) {\n seen.set(key, route);\n }\n }\n return [...seen.values()];\n}\n\n/**\n * Check if a URL path matches an ignore pattern.\n * Supports simple wildcards: /external/** matches /external/anything\n */\nexport function matchIgnorePattern(path: string, pattern: string): boolean {\n if (pattern.endsWith('/**')) {\n const prefix = pattern.slice(0, -3);\n return path.startsWith(prefix);\n }\n return path === pattern;\n}\n","import type { RouteDescriptor, ConflictWarning, ConflictDetail } from '../types.js';\n\n/**\n * Detect conflicts between multiple declarations of the same route.\n * Must be called on the pre-deduplicated route list (before first-wins dedup).\n */\nexport function detectConflicts(routes: RouteDescriptor[]): ConflictWarning[] {\n const warnings: ConflictWarning[] = [];\n\n // Group routes by method:path\n const groups = new Map<string, RouteDescriptor[]>();\n for (const route of routes) {\n const key = `${route.method}:${route.path}`;\n const existing = groups.get(key) ?? [];\n existing.push(route);\n groups.set(key, existing);\n }\n\n for (const [key, groupRoutes] of groups) {\n if (groupRoutes.length < 2) continue;\n\n const conflicts: ConflictDetail[] = [];\n\n // Check responseFormat\n const formats = [...new Set(groupRoutes.map(r => r.responseFormat))];\n if (formats.length > 1) {\n conflicts.push({\n field: 'responseFormat',\n values: formats,\n message: `Conflicting response formats: ${formats.join(' vs ')}`,\n });\n }\n\n // Check requestBody field names (for POST/PUT/PATCH)\n if (['POST', 'PUT', 'PATCH'].includes(groupRoutes[0].method)) {\n const bodySignatures = groupRoutes.map(r =>\n JSON.stringify((r.requestBody ?? []).map(f => f.name).sort())\n );\n const uniqueSignatures = [...new Set(bodySignatures)];\n if (uniqueSignatures.length > 1) {\n conflicts.push({\n field: 'requestBody',\n values: uniqueSignatures,\n message: 'Request body fields differ across declarations',\n });\n }\n }\n\n // Check queryParams\n const querySignatures = groupRoutes.map(r =>\n JSON.stringify((r.queryParams ?? []).map(p => p.name).sort())\n );\n const uniqueQuerySigs = [...new Set(querySignatures)];\n if (uniqueQuerySigs.length > 1) {\n conflicts.push({\n field: 'queryParams',\n values: uniqueQuerySigs,\n message: 'Query parameters differ across declarations',\n });\n }\n\n if (conflicts.length > 0) {\n warnings.push({\n routeKey: key,\n conflicts,\n sources: groupRoutes.map(r => r.source),\n });\n }\n }\n\n return warnings;\n}\n\n/**\n * Format conflict warnings for human-readable CLI output.\n */\nexport function formatConflicts(warnings: ConflictWarning[]): string {\n const lines: string[] = [];\n\n for (const warning of warnings) {\n lines.push(` ${warning.routeKey}:`);\n for (const conflict of warning.conflicts) {\n lines.push(` - ${conflict.message}`);\n }\n lines.push(` Sources:`);\n for (const source of warning.sources) {\n lines.push(` ${source.file}:${source.line ?? '?'} (${source.kind})`);\n }\n }\n\n return lines.join('\\n');\n}\n","import type { RouteDescriptor } from '../types.js';\n\n/** Marker comment indicating a serverbridge-generated route */\nexport const ROUTE_MARKER = '// @serverbridge-route:';\n/** Marker for start of user-editable code */\nexport const USER_START = '// @serverbridge-user-start';\n/** Marker for end of user-editable code */\nexport const USER_END = '// @serverbridge-user-end';\n\n/**\n * Generate a route comment header.\n */\nexport function routeMarker(route: RouteDescriptor): string {\n return `${ROUTE_MARKER} ${route.method} ${route.path}`;\n}\n\n/**\n * Generate a TODO comment block for an unimplemented route.\n */\nexport function todoBlock(route: RouteDescriptor, indent: string = ' '): string {\n const lines: string[] = [];\n lines.push(`${indent}// TODO: Implement ${route.method} ${route.path}`);\n\n if (route.pathParams.length > 0) {\n lines.push(`${indent}// Path params: ${route.pathParams.join(', ')}`);\n }\n\n if (route.queryParams && route.queryParams.length > 0) {\n lines.push(`${indent}// Query params: ${route.queryParams.map(p => p.name).join(', ')}`);\n }\n\n if (route.requestBody && route.requestBody.length > 0) {\n const fields = route.requestBody\n .map(f => `${f.name}: ${f.type}${f.required ? '' : '?'}`)\n .join(', ');\n lines.push(`${indent}// Expected body: { ${fields} }`);\n }\n\n if (route.notes.length > 0) {\n for (const note of route.notes) {\n lines.push(`${indent}// Note: ${note}`);\n }\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Group routes by common path prefix for organizing into files.\n * E.g., /api/users and /api/users/:id group under \"api-users\".\n */\nexport function groupRoutesByPrefix(routes: RouteDescriptor[]): Map<string, RouteDescriptor[]> {\n const groups = new Map<string, RouteDescriptor[]>();\n\n for (const route of routes) {\n // Take first two segments as the group key\n const segments = route.path.replace(/^\\//, '').split('/');\n const groupKey =\n segments\n .slice(0, Math.min(2, segments.length))\n .filter(s => !s.startsWith(':'))\n .join('-') || 'root';\n\n const group = groups.get(groupKey) ?? [];\n group.push(route);\n groups.set(groupKey, group);\n }\n\n return groups;\n}\n\n/**\n * Generate a safe filename from a group key.\n */\nexport function groupToFilename(groupKey: string, typescript: boolean): string {\n return `${groupKey}.${typescript ? 'ts' : 'js'}`;\n}\n","import type { RouteDescriptor, GeneratorOptions, GenerateResult, GeneratedFile } from '../types.js';\nimport type { RouteGenerator } from './types.js';\nimport {\n routeMarker,\n todoBlock,\n USER_START,\n USER_END,\n groupRoutesByPrefix,\n groupToFilename,\n} from './template-helpers.js';\n\nexport class ExpressGenerator implements RouteGenerator {\n readonly framework = 'express';\n\n generate(routes: RouteDescriptor[], options: GeneratorOptions): GenerateResult {\n const ts = options.typescript !== false; // default true\n const files: GeneratedFile[] = [];\n const warnings: string[] = [];\n\n if (routes.length === 0) {\n warnings.push('No routes to generate');\n return { files, preserved: [], warnings };\n }\n\n const groups = groupRoutesByPrefix(routes);\n\n // Generate one router file per group\n for (const [groupKey, groupRoutes] of groups) {\n const filename = groupToFilename(groupKey, ts);\n const content = this.generateRouterFile(groupRoutes, groupKey, ts);\n files.push({ path: `routes/${filename}`, content, isNew: true });\n }\n\n // Generate index file that imports all routers\n const indexContent = this.generateIndex(groups, ts);\n files.push({ path: `routes/index.${ts ? 'ts' : 'js'}`, content: indexContent, isNew: true });\n\n return { files, preserved: [], warnings };\n }\n\n private generateRouterFile(routes: RouteDescriptor[], groupKey: string, ts: boolean): string {\n const lines: string[] = [];\n\n lines.push(`// Generated by @hyperfixi/server-bridge — DO NOT EDIT scaffold markers`);\n lines.push(`import { Router${ts ? ', Request, Response' : ''} } from 'express';`);\n lines.push('');\n lines.push('const router = Router();');\n lines.push('');\n\n for (const route of routes) {\n lines.push(routeMarker(route));\n lines.push(this.generateHandler(route, ts));\n lines.push('');\n }\n\n lines.push('export default router;');\n lines.push('');\n\n return lines.join('\\n');\n }\n\n private generateHandler(route: RouteDescriptor, ts: boolean): string {\n const method = route.method.toLowerCase();\n const params = ts ? '(req: Request, res: Response)' : '(req, res)';\n const lines: string[] = [];\n\n lines.push(`router.${method}('${route.path}', async ${params} => {`);\n lines.push(` ${USER_START}`);\n lines.push(todoBlock(route, ' '));\n\n // Destructure path params\n if (route.pathParams.length > 0) {\n const paramList = route.pathParams.join(', ');\n lines.push(` const { ${paramList} } = req.params;`);\n }\n\n // Destructure query params\n if (route.queryParams && route.queryParams.length > 0) {\n const paramList = route.queryParams.map(p => p.name).join(', ');\n lines.push(` const { ${paramList} } = req.query;`);\n }\n\n // Destructure body fields for POST/PUT/PATCH\n if (\n route.requestBody &&\n route.requestBody.length > 0 &&\n ['POST', 'PUT', 'PATCH'].includes(route.method)\n ) {\n const fieldList = route.requestBody.map(f => f.name).join(', ');\n lines.push(` const { ${fieldList} } = req.body;`);\n }\n\n // Response stub\n if (route.responseFormat === 'json') {\n lines.push(` res.json({ message: 'Not implemented' });`);\n } else if (route.responseFormat === 'html') {\n lines.push(` res.send('<div>Not implemented</div>');`);\n } else {\n lines.push(` res.send('Not implemented');`);\n }\n\n lines.push(` ${USER_END}`);\n lines.push(`});`);\n\n return lines.join('\\n');\n }\n\n private generateIndex(groups: Map<string, RouteDescriptor[]>, ts: boolean): string {\n const lines: string[] = [];\n\n lines.push(`// Generated by @hyperfixi/server-bridge — DO NOT EDIT`);\n lines.push(`import { Router } from 'express';`);\n\n for (const groupKey of groups.keys()) {\n const filename = groupKey;\n lines.push(`import ${toCamelCase(groupKey)}Router from './${filename}.js';`);\n }\n\n lines.push('');\n lines.push('const router = Router();');\n lines.push('');\n\n for (const groupKey of groups.keys()) {\n lines.push(`router.use(${toCamelCase(groupKey)}Router);`);\n }\n\n lines.push('');\n lines.push('export default router;');\n lines.push('');\n\n return lines.join('\\n');\n }\n}\n\nfunction toCamelCase(str: string): string {\n return str.replace(/-(\\w)/g, (_, c) => c.toUpperCase());\n}\n","import type { RouteDescriptor, GeneratorOptions, GenerateResult, GeneratedFile } from '../types.js';\nimport type { RouteGenerator } from './types.js';\nimport {\n routeMarker,\n todoBlock,\n USER_START,\n USER_END,\n groupRoutesByPrefix,\n groupToFilename,\n} from './template-helpers.js';\n\nexport class HonoGenerator implements RouteGenerator {\n readonly framework = 'hono';\n\n generate(routes: RouteDescriptor[], options: GeneratorOptions): GenerateResult {\n const ts = options.typescript !== false;\n const files: GeneratedFile[] = [];\n const warnings: string[] = [];\n\n if (routes.length === 0) {\n warnings.push('No routes to generate');\n return { files, preserved: [], warnings };\n }\n\n const groups = groupRoutesByPrefix(routes);\n\n for (const [groupKey, groupRoutes] of groups) {\n const filename = groupToFilename(groupKey, ts);\n const content = this.generateRouteFile(groupRoutes, groupKey, ts);\n files.push({ path: `routes/${filename}`, content, isNew: true });\n }\n\n const indexContent = this.generateIndex(groups, ts);\n files.push({ path: `routes/index.${ts ? 'ts' : 'js'}`, content: indexContent, isNew: true });\n\n return { files, preserved: [], warnings };\n }\n\n private generateRouteFile(routes: RouteDescriptor[], groupKey: string, ts: boolean): string {\n const lines: string[] = [];\n\n lines.push(`// Generated by @hyperfixi/server-bridge — DO NOT EDIT scaffold markers`);\n lines.push(`import { Hono } from 'hono';`);\n lines.push('');\n lines.push('const app = new Hono();');\n lines.push('');\n\n for (const route of routes) {\n lines.push(routeMarker(route));\n lines.push(this.generateHandler(route, ts));\n lines.push('');\n }\n\n lines.push('export default app;');\n lines.push('');\n\n return lines.join('\\n');\n }\n\n private generateHandler(route: RouteDescriptor, ts: boolean): string {\n const method = route.method.toLowerCase();\n const lines: string[] = [];\n\n lines.push(`app.${method}('${route.path}', async (c) => {`);\n lines.push(` ${USER_START}`);\n lines.push(todoBlock(route, ' '));\n\n // Destructure path params\n if (route.pathParams.length > 0) {\n for (const param of route.pathParams) {\n lines.push(` const ${param} = c.req.param('${param}');`);\n }\n }\n\n // Extract query params\n if (route.queryParams && route.queryParams.length > 0) {\n for (const param of route.queryParams) {\n lines.push(` const ${param.name} = c.req.query('${param.name}');`);\n }\n }\n\n // Parse body for POST/PUT/PATCH\n if (\n route.requestBody &&\n route.requestBody.length > 0 &&\n ['POST', 'PUT', 'PATCH'].includes(route.method)\n ) {\n lines.push(` const body = await c.req.json();`);\n const fieldList = route.requestBody.map(f => f.name).join(', ');\n lines.push(` const { ${fieldList} } = body;`);\n }\n\n // Response stub\n if (route.responseFormat === 'json') {\n lines.push(` return c.json({ message: 'Not implemented' });`);\n } else if (route.responseFormat === 'html') {\n lines.push(` return c.html('<div>Not implemented</div>');`);\n } else {\n lines.push(` return c.text('Not implemented');`);\n }\n\n lines.push(` ${USER_END}`);\n lines.push(`});`);\n\n return lines.join('\\n');\n }\n\n private generateIndex(groups: Map<string, RouteDescriptor[]>, ts: boolean): string {\n const lines: string[] = [];\n\n lines.push(`// Generated by @hyperfixi/server-bridge — DO NOT EDIT`);\n lines.push(`import { Hono } from 'hono';`);\n\n for (const groupKey of groups.keys()) {\n const filename = groupKey;\n lines.push(`import ${toCamelCase(groupKey)}Routes from './${filename}.js';`);\n }\n\n lines.push('');\n lines.push('const app = new Hono();');\n lines.push('');\n\n for (const groupKey of groups.keys()) {\n lines.push(`app.route('/', ${toCamelCase(groupKey)}Routes);`);\n }\n\n lines.push('');\n lines.push('export default app;');\n lines.push('');\n\n return lines.join('\\n');\n }\n}\n\nfunction toCamelCase(str: string): string {\n return str.replace(/-(\\w)/g, (_, c) => c.toUpperCase());\n}\n","import type { RouteDescriptor, GeneratorOptions, GenerateResult, GeneratedFile } from '../types.js';\nimport type { RouteGenerator } from './types.js';\n\nexport class OpenAPIGenerator implements RouteGenerator {\n readonly framework = 'openapi';\n\n generate(routes: RouteDescriptor[], options: GeneratorOptions): GenerateResult {\n const files: GeneratedFile[] = [];\n const warnings: string[] = [];\n\n if (routes.length === 0) {\n warnings.push('No routes to generate');\n return { files, preserved: [], warnings };\n }\n\n const spec = this.buildSpec(routes);\n const yaml = this.toYaml(spec);\n\n files.push({\n path: 'openapi.yaml',\n content: yaml,\n isNew: true,\n });\n\n return { files, preserved: [], warnings };\n }\n\n private buildSpec(routes: RouteDescriptor[]): OpenAPISpec {\n const paths: Record<string, Record<string, OpenAPIOperation>> = {};\n\n for (const route of routes) {\n // Convert :param to {param} for OpenAPI\n const oaPath = route.path.replace(/:(\\w+)/g, '{$1}');\n const method = route.method.toLowerCase();\n\n if (!paths[oaPath]) {\n paths[oaPath] = {};\n }\n\n paths[oaPath][method] = this.buildOperation(route);\n }\n\n return {\n openapi: '3.1.0',\n info: {\n title: 'ServerBridge Generated API',\n version: '0.1.0',\n description: 'Auto-extracted from hyperscript/htmx attributes',\n },\n paths,\n };\n }\n\n private buildOperation(route: RouteDescriptor): OpenAPIOperation {\n const op: OpenAPIOperation = {\n operationId: route.handlerName,\n summary: `Source: ${route.source.file}${route.source.line ? ':' + route.source.line : ''} (${route.source.kind})`,\n responses: {\n '200': this.buildResponse(route),\n },\n };\n\n // Path parameters\n if (route.pathParams.length > 0) {\n op.parameters = route.pathParams.map(name => ({\n name,\n in: 'path' as const,\n required: true,\n schema: { type: inferParamType(name) },\n }));\n }\n\n // Query parameters\n if (route.queryParams && route.queryParams.length > 0) {\n const queryParamSpecs = route.queryParams.map(param => ({\n name: param.name,\n in: 'query' as const,\n required: param.required,\n schema: { type: 'string' as const },\n }));\n if (op.parameters) {\n op.parameters.push(...queryParamSpecs);\n } else {\n op.parameters = queryParamSpecs;\n }\n }\n\n // Request body\n if (\n route.requestBody &&\n route.requestBody.length > 0 &&\n ['POST', 'PUT', 'PATCH'].includes(route.method)\n ) {\n const required = route.requestBody.filter(f => f.required).map(f => f.name);\n const properties: Record<string, { type: string; format?: string }> = {};\n\n for (const field of route.requestBody) {\n properties[field.name] = fieldToSchema(field.type);\n }\n\n op.requestBody = {\n required: required.length > 0,\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n ...(required.length > 0 ? { required } : {}),\n properties,\n },\n },\n },\n };\n }\n\n return op;\n }\n\n private buildResponse(route: RouteDescriptor): OpenAPIResponse {\n const contentType =\n route.responseFormat === 'json'\n ? 'application/json'\n : route.responseFormat === 'html'\n ? 'text/html'\n : 'text/plain';\n\n return {\n description: 'Success',\n content: {\n [contentType]: {\n schema: route.responseFormat === 'json' ? { type: 'object' } : { type: 'string' },\n },\n },\n };\n }\n\n /**\n * Minimal YAML serializer — avoids adding a yaml dependency.\n * Handles the specific nested structure of OpenAPI specs.\n */\n toYaml(obj: unknown, indent: number = 0): string {\n const pad = ' '.repeat(indent);\n\n if (obj === null || obj === undefined) {\n return 'null';\n }\n\n if (typeof obj === 'string') {\n // Quote strings that could be ambiguous\n if (\n obj === '' ||\n obj.includes(':') ||\n obj.includes('#') ||\n obj.includes(\"'\") ||\n /^\\d/.test(obj) ||\n obj === 'true' ||\n obj === 'false'\n ) {\n return `'${obj.replace(/'/g, \"''\")}'`;\n }\n return obj;\n }\n\n if (typeof obj === 'number' || typeof obj === 'boolean') {\n return String(obj);\n }\n\n if (Array.isArray(obj)) {\n if (obj.length === 0) return '[]';\n\n // For simple string/number arrays, use inline format\n if (obj.every(item => typeof item === 'string' || typeof item === 'number')) {\n return `[${obj.map(item => (typeof item === 'string' ? `'${item}'` : String(item))).join(', ')}]`;\n }\n\n return obj\n .map(item => {\n if (typeof item === 'object' && item !== null) {\n const inner = this.toYaml(item, indent + 1).trimStart();\n return `${pad}- ${inner}`;\n }\n return `${pad}- ${this.toYaml(item)}`;\n })\n .join('\\n');\n }\n\n if (typeof obj === 'object') {\n const entries = Object.entries(obj as Record<string, unknown>);\n if (entries.length === 0) return '{}';\n\n return entries\n .map(([key, value]) => {\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n const inner = this.toYaml(value, indent + 1);\n return `${pad}${key}:\\n${inner}`;\n }\n if (Array.isArray(value) && value.length > 0 && value.some(v => typeof v === 'object')) {\n const inner = this.toYaml(value, indent + 1);\n return `${pad}${key}:\\n${inner}`;\n }\n return `${pad}${key}: ${this.toYaml(value, indent + 1)}`;\n })\n .join('\\n');\n }\n\n return String(obj);\n }\n}\n\n// --- Helper types (not exported) ---\n\ninterface OpenAPISpec {\n openapi: string;\n info: { title: string; version: string; description: string };\n paths: Record<string, Record<string, OpenAPIOperation>>;\n}\n\ninterface OpenAPIOperation {\n operationId: string;\n summary: string;\n parameters?: OpenAPIParameter[];\n requestBody?: OpenAPIRequestBody;\n responses: Record<string, OpenAPIResponse>;\n}\n\ninterface OpenAPIParameter {\n name: string;\n in: 'path' | 'query';\n required: boolean;\n schema: { type: string };\n}\n\ninterface OpenAPIRequestBody {\n required: boolean;\n content: Record<string, { schema: Record<string, unknown> }>;\n}\n\ninterface OpenAPIResponse {\n description: string;\n content: Record<string, { schema: { type: string } }>;\n}\n\nfunction inferParamType(name: string): string {\n if (name === 'id' || name.endsWith('Id')) return 'string';\n if (name === 'page' || name === 'limit' || name === 'offset') return 'integer';\n return 'string';\n}\n\nfunction fieldToSchema(type: string): { type: string; format?: string } {\n switch (type) {\n case 'number':\n return { type: 'number' };\n case 'boolean':\n return { type: 'boolean' };\n case 'file':\n return { type: 'string', format: 'binary' };\n default:\n return { type: 'string' };\n }\n}\n","import type { RouteDescriptor, GeneratorOptions, GenerateResult, GeneratedFile } from '../types.js';\nimport type { RouteGenerator } from './types.js';\nimport { groupRoutesByPrefix } from './template-helpers.js';\n\nconst PY_ROUTE_MARKER = '# @serverbridge-route:';\nconst PY_USER_START = '# @serverbridge-user-start';\nconst PY_USER_END = '# @serverbridge-user-end';\n\nexport class DjangoGenerator implements RouteGenerator {\n readonly framework = 'django';\n\n generate(routes: RouteDescriptor[], options: GeneratorOptions): GenerateResult {\n const files: GeneratedFile[] = [];\n const warnings: string[] = [];\n\n if (routes.length === 0) {\n warnings.push('No routes to generate');\n return { files, preserved: [], warnings };\n }\n\n const groups = groupRoutesByPrefix(routes);\n\n // Generate views.py and urls.py per group\n for (const [groupKey, groupRoutes] of groups) {\n const views = this.generateViews(groupRoutes, groupKey);\n files.push({ path: `${groupKey}/views.py`, content: views, isNew: true });\n\n const urls = this.generateUrls(groupRoutes, groupKey);\n files.push({ path: `${groupKey}/urls.py`, content: urls, isNew: true });\n\n // Empty __init__.py for Python package\n files.push({ path: `${groupKey}/__init__.py`, content: '', isNew: true });\n }\n\n // Root urls.py that includes all groups\n const rootUrls = this.generateRootUrls(groups);\n files.push({ path: 'urls.py', content: rootUrls, isNew: true });\n\n return { files, preserved: [], warnings };\n }\n\n private generateViews(routes: RouteDescriptor[], groupKey: string): string {\n const lines: string[] = [];\n\n lines.push(`# Generated by @hyperfixi/server-bridge — DO NOT EDIT scaffold markers`);\n lines.push(`from django.http import JsonResponse, HttpResponse`);\n\n // Check if any route needs request body parsing\n const needsJson = routes.some(\n r => r.requestBody && r.requestBody.length > 0 && ['POST', 'PUT', 'PATCH'].includes(r.method)\n );\n if (needsJson) {\n lines.push(`import json`);\n }\n\n // Check if we need decorators\n const hasNonGet = routes.some(r => r.method !== 'GET');\n if (hasNonGet) {\n lines.push(`from django.views.decorators.http import require_http_methods`);\n }\n\n lines.push('');\n\n for (const route of routes) {\n lines.push('');\n lines.push(`${PY_ROUTE_MARKER} ${route.method} ${route.path}`);\n lines.push(this.generateView(route));\n }\n\n lines.push('');\n return lines.join('\\n');\n }\n\n private generateView(route: RouteDescriptor): string {\n const lines: string[] = [];\n const funcName = toSnakeCase(route.handlerName);\n\n // Method decorator for non-GET routes\n if (route.method !== 'GET') {\n lines.push(`@require_http_methods([\"${route.method}\"])`);\n }\n\n // Function signature with path params\n const params = ['request', ...route.pathParams];\n lines.push(`def ${funcName}(${params.join(', ')}):`);\n lines.push(` ${PY_USER_START}`);\n\n // TODO block\n lines.push(` # TODO: Implement ${route.method} ${route.path}`);\n if (route.pathParams.length > 0) {\n lines.push(` # Path params: ${route.pathParams.join(', ')}`);\n }\n if (route.requestBody && route.requestBody.length > 0) {\n const fields = route.requestBody\n .map(f => `${f.name}: ${pythonType(f.type)}${f.required ? '' : ' (optional)'}`)\n .join(', ');\n lines.push(` # Expected body: { ${fields} }`);\n }\n for (const note of route.notes) {\n lines.push(` # Note: ${note}`);\n }\n\n // Extract query params\n if (route.queryParams && route.queryParams.length > 0) {\n for (const param of route.queryParams) {\n lines.push(` ${param.name} = request.GET.get('${param.name}')`);\n }\n }\n\n // Parse body for POST/PUT/PATCH\n if (\n route.requestBody &&\n route.requestBody.length > 0 &&\n ['POST', 'PUT', 'PATCH'].includes(route.method)\n ) {\n lines.push(` data = json.loads(request.body)`);\n for (const field of route.requestBody) {\n const default_ = field.required ? '' : ', None';\n lines.push(` ${field.name} = data.get('${field.name}'${default_})`);\n }\n }\n\n // Response stub\n if (route.responseFormat === 'json') {\n lines.push(` return JsonResponse({'message': 'Not implemented'})`);\n } else if (route.responseFormat === 'html') {\n lines.push(` return HttpResponse('<div>Not implemented</div>')`);\n } else {\n lines.push(` return HttpResponse('Not implemented', content_type='text/plain')`);\n }\n\n lines.push(` ${PY_USER_END}`);\n return lines.join('\\n');\n }\n\n private generateUrls(routes: RouteDescriptor[], groupKey: string): string {\n const lines: string[] = [];\n\n lines.push(`# Generated by @hyperfixi/server-bridge — DO NOT EDIT`);\n lines.push(`from django.urls import path`);\n lines.push(`from . import views`);\n lines.push('');\n lines.push('urlpatterns = [');\n\n for (const route of routes) {\n const djangoPath = toDjangoPath(route.path);\n const funcName = toSnakeCase(route.handlerName);\n lines.push(` path('${djangoPath}', views.${funcName}, name='${funcName}'),`);\n }\n\n lines.push(']');\n lines.push('');\n\n return lines.join('\\n');\n }\n\n private generateRootUrls(groups: Map<string, RouteDescriptor[]>): string {\n const lines: string[] = [];\n\n lines.push(`# Generated by @hyperfixi/server-bridge — DO NOT EDIT`);\n lines.push(`from django.urls import path, include`);\n lines.push('');\n lines.push('urlpatterns = [');\n\n for (const groupKey of groups.keys()) {\n lines.push(` path('', include('${groupKey}.urls')),`);\n }\n\n lines.push(']');\n lines.push('');\n\n return lines.join('\\n');\n }\n}\n\n/**\n * Convert camelCase handler name to snake_case for Python.\n */\nfunction toSnakeCase(str: string): string {\n return str\n .replace(/([A-Z])/g, '_$1')\n .toLowerCase()\n .replace(/^_/, '');\n}\n\n/**\n * Convert Express-style :param path to Django <param> path.\n * \"/api/users/:id\" → \"api/users/<int:id>\"\n */\nfunction toDjangoPath(path: string): string {\n return path\n .replace(/^\\//, '') // Strip leading slash (Django convention)\n .replace(/:(\\w+)/g, '<str:$1>'); // :param → <str:param>\n}\n\n/**\n * Map our field types to Python type hints.\n */\nfunction pythonType(type: string): string {\n switch (type) {\n case 'number':\n return 'int';\n case 'boolean':\n return 'bool';\n case 'file':\n return 'UploadedFile';\n default:\n return 'str';\n }\n}\n","import type { RouteDescriptor, GeneratorOptions, GenerateResult, GeneratedFile } from '../types.js';\nimport type { RouteGenerator } from './types.js';\nimport { groupRoutesByPrefix } from './template-helpers.js';\n\nconst PY_ROUTE_MARKER = '# @serverbridge-route:';\nconst PY_USER_START = '# @serverbridge-user-start';\nconst PY_USER_END = '# @serverbridge-user-end';\n\nexport class FastAPIGenerator implements RouteGenerator {\n readonly framework = 'fastapi';\n\n generate(routes: RouteDescriptor[], options: GeneratorOptions): GenerateResult {\n const files: GeneratedFile[] = [];\n const warnings: string[] = [];\n\n if (routes.length === 0) {\n warnings.push('No routes to generate');\n return { files, preserved: [], warnings };\n }\n\n const groups = groupRoutesByPrefix(routes);\n\n for (const [groupKey, groupRoutes] of groups) {\n const content = this.generateRouterFile(groupRoutes, groupKey);\n const pyFilename = groupKey.replace(/-/g, '_');\n files.push({ path: `routers/${pyFilename}.py`, content, isNew: true });\n }\n\n // Main app file that includes all routers\n const mainContent = this.generateMain(groups);\n files.push({ path: 'main.py', content: mainContent, isNew: true });\n\n // Pydantic models file if any routes have request bodies\n const allRoutes = [...groups.values()].flat();\n const routesWithBodies = allRoutes.filter(\n r => r.requestBody && r.requestBody.length > 0 && ['POST', 'PUT', 'PATCH'].includes(r.method)\n );\n if (routesWithBodies.length > 0) {\n const models = this.generateModels(routesWithBodies);\n files.push({ path: 'models.py', content: models, isNew: true });\n }\n\n return { files, preserved: [], warnings };\n }\n\n private generateRouterFile(routes: RouteDescriptor[], groupKey: string): string {\n const lines: string[] = [];\n\n lines.push(`# Generated by @hyperfixi/server-bridge — DO NOT EDIT scaffold markers`);\n\n // Check if we need Query import\n const needsQuery = routes.some(r => r.queryParams && r.queryParams.length > 0);\n if (needsQuery) {\n lines.push(`from fastapi import APIRouter, Query`);\n } else {\n lines.push(`from fastapi import APIRouter`);\n }\n\n // Check if we need models import\n const routesWithBodies = routes.filter(\n r => r.requestBody && r.requestBody.length > 0 && ['POST', 'PUT', 'PATCH'].includes(r.method)\n );\n if (routesWithBodies.length > 0) {\n const modelNames = routesWithBodies.map(r => modelName(r.handlerName));\n lines.push(`from ..models import ${modelNames.join(', ')}`);\n }\n\n // Check response types needed\n const needsHtml = routes.some(r => r.responseFormat === 'html');\n const needsPlaintext = routes.some(r => r.responseFormat === 'text');\n if (needsHtml || needsPlaintext) {\n lines.push(`from fastapi.responses import HTMLResponse, PlainTextResponse`);\n }\n\n lines.push('');\n lines.push(`router = APIRouter()`);\n lines.push('');\n\n for (const route of routes) {\n lines.push('');\n lines.push(`${PY_ROUTE_MARKER} ${route.method} ${route.path}`);\n lines.push(this.generateEndpoint(route));\n }\n\n lines.push('');\n return lines.join('\\n');\n }\n\n private generateEndpoint(route: RouteDescriptor): string {\n const lines: string[] = [];\n const method = route.method.toLowerCase();\n const funcName = toSnakeCase(route.handlerName);\n const fastapiPath = toFastAPIPath(route.path);\n\n // Response class for non-JSON\n const extraArgs: string[] = [];\n if (route.responseFormat === 'html') {\n extraArgs.push('response_class=HTMLResponse');\n } else if (route.responseFormat === 'text') {\n extraArgs.push('response_class=PlainTextResponse');\n }\n\n const argsStr = extraArgs.length > 0 ? `, ${extraArgs.join(', ')}` : '';\n lines.push(`@router.${method}('${fastapiPath}'${argsStr})`);\n\n // Function signature with typed params\n const params: string[] = [];\n for (const param of route.pathParams) {\n params.push(`${param}: str`);\n }\n if (route.queryParams && route.queryParams.length > 0) {\n for (const qp of route.queryParams) {\n params.push(`${qp.name}: str = Query(...)`);\n }\n }\n if (\n route.requestBody &&\n route.requestBody.length > 0 &&\n ['POST', 'PUT', 'PATCH'].includes(route.method)\n ) {\n params.push(`body: ${modelName(route.handlerName)}`);\n }\n\n lines.push(`async def ${funcName}(${params.join(', ')}):`);\n lines.push(` ${PY_USER_START}`);\n\n // TODO block\n lines.push(` # TODO: Implement ${route.method} ${route.path}`);\n if (route.pathParams.length > 0) {\n lines.push(` # Path params: ${route.pathParams.join(', ')}`);\n }\n if (route.requestBody && route.requestBody.length > 0) {\n const fields = route.requestBody\n .map(f => `${f.name}: ${pythonType(f.type)}${f.required ? '' : ' (optional)'}`)\n .join(', ');\n lines.push(` # Expected body: { ${fields} }`);\n }\n for (const note of route.notes) {\n lines.push(` # Note: ${note}`);\n }\n\n // Response stub\n if (route.responseFormat === 'json') {\n lines.push(` return {'message': 'Not implemented'}`);\n } else if (route.responseFormat === 'html') {\n lines.push(` return '<div>Not implemented</div>'`);\n } else {\n lines.push(` return 'Not implemented'`);\n }\n\n lines.push(` ${PY_USER_END}`);\n return lines.join('\\n');\n }\n\n private generateMain(groups: Map<string, RouteDescriptor[]>): string {\n const lines: string[] = [];\n\n lines.push(`# Generated by @hyperfixi/server-bridge — DO NOT EDIT`);\n lines.push(`from fastapi import FastAPI`);\n\n for (const groupKey of groups.keys()) {\n const pyName = groupKey.replace(/-/g, '_');\n lines.push(`from .routers.${pyName} import router as ${pyName}_router`);\n }\n\n lines.push('');\n lines.push('app = FastAPI()');\n lines.push('');\n\n for (const groupKey of groups.keys()) {\n const pyName = groupKey.replace(/-/g, '_');\n lines.push(`app.include_router(${pyName}_router)`);\n }\n\n lines.push('');\n return lines.join('\\n');\n }\n\n private generateModels(routes: RouteDescriptor[]): string {\n const lines: string[] = [];\n\n lines.push(`# Generated by @hyperfixi/server-bridge — DO NOT EDIT`);\n lines.push(`from pydantic import BaseModel`);\n lines.push(`from typing import Optional`);\n lines.push('');\n\n for (const route of routes) {\n if (!route.requestBody || route.requestBody.length === 0) continue;\n\n lines.push('');\n lines.push(`class ${modelName(route.handlerName)}(BaseModel):`);\n\n for (const field of route.requestBody) {\n const pyType = pythonType(field.type);\n if (field.required) {\n lines.push(` ${field.name}: ${pyType}`);\n } else {\n lines.push(` ${field.name}: Optional[${pyType}] = None`);\n }\n }\n }\n\n lines.push('');\n return lines.join('\\n');\n }\n}\n\n/**\n * Convert camelCase handler name to snake_case for Python.\n */\nfunction toSnakeCase(str: string): string {\n return str\n .replace(/([A-Z])/g, '_$1')\n .toLowerCase()\n .replace(/^_/, '');\n}\n\n/**\n * Convert Express-style :param path to FastAPI {param} path.\n * \"/api/users/:id\" → \"/api/users/{id}\"\n */\nfunction toFastAPIPath(path: string): string {\n return path.replace(/:(\\w+)/g, '{$1}');\n}\n\n/**\n * Generate a Pydantic model name from handler name.\n * \"postApiUsers\" → \"PostApiUsersBody\"\n */\nfunction modelName(handlerName: string): string {\n return handlerName.charAt(0).toUpperCase() + handlerName.slice(1) + 'Body';\n}\n\n/**\n * Map our field types to Python type hints.\n */\nfunction pythonType(type: string): string {\n switch (type) {\n case 'number':\n return 'int';\n case 'boolean':\n return 'bool';\n case 'file':\n return 'bytes';\n default:\n return 'str';\n }\n}\n","import { ExpressGenerator } from './express-generator.js';\nimport { HonoGenerator } from './hono-generator.js';\nimport { OpenAPIGenerator } from './openapi-generator.js';\nimport { DjangoGenerator } from './django-generator.js';\nimport { FastAPIGenerator } from './fastapi-generator.js';\nimport type { RouteGenerator } from './types.js';\n\n/**\n * Select the appropriate RouteGenerator for a framework name.\n * Falls back to ExpressGenerator for unrecognized values.\n */\nexport function selectGenerator(framework: string): RouteGenerator {\n switch (framework) {\n case 'hono':\n return new HonoGenerator();\n case 'openapi':\n return new OpenAPIGenerator();\n case 'django':\n return new DjangoGenerator();\n case 'fastapi':\n return new FastAPIGenerator();\n default:\n return new ExpressGenerator();\n }\n}\n","import { readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { createHash } from 'node:crypto';\nimport type { RouteDescriptor } from '../types.js';\nimport { USER_START, USER_END } from './template-helpers.js';\n\nconst MANIFEST_FILENAME = 'server-bridge.manifest.json';\n\nexport interface ManifestEntry {\n method: string;\n path: string;\n handlerName: string;\n hash: string;\n}\n\nexport interface Manifest {\n version: number;\n generatedAt: string;\n routes: Record<string, ManifestEntry>;\n}\n\n/**\n * Create a hash for a RouteDescriptor to detect changes.\n */\nexport function hashRoute(route: RouteDescriptor): string {\n const data = JSON.stringify({\n path: route.path,\n method: route.method,\n responseFormat: route.responseFormat,\n pathParams: route.pathParams,\n queryParams: route.queryParams,\n requestBody: route.requestBody,\n });\n return createHash('md5').update(data).digest('hex').slice(0, 8);\n}\n\n/**\n * Build a manifest key from method + path.\n */\nfunction manifestKey(method: string, path: string): string {\n return `${method}:${path}`;\n}\n\n/**\n * Create a new manifest from a set of routes.\n */\nexport function createManifest(routes: RouteDescriptor[]): Manifest {\n const manifest: Manifest = {\n version: 1,\n generatedAt: new Date().toISOString(),\n routes: {},\n };\n\n for (const route of routes) {\n const key = manifestKey(route.method, route.path);\n manifest.routes[key] = {\n method: route.method,\n path: route.path,\n handlerName: route.handlerName,\n hash: hashRoute(route),\n };\n }\n\n return manifest;\n}\n\n/**\n * Load an existing manifest from the output directory.\n * Returns null if no manifest exists.\n */\nexport async function loadManifest(outputDir: string): Promise<Manifest | null> {\n try {\n const content = await readFile(join(outputDir, MANIFEST_FILENAME), 'utf-8');\n return JSON.parse(content) as Manifest;\n } catch {\n return null;\n }\n}\n\n/**\n * Save a manifest to the output directory.\n */\nexport async function saveManifest(outputDir: string, manifest: Manifest): Promise<void> {\n await writeFile(\n join(outputDir, MANIFEST_FILENAME),\n JSON.stringify(manifest, null, 2) + '\\n',\n 'utf-8'\n );\n}\n\n/**\n * Extract user-written code between @serverbridge-user-start and @serverbridge-user-end markers.\n * Returns a map from route key (METHOD:path) to user code string.\n */\nexport function extractUserCode(fileContent: string): Map<string, string> {\n const result = new Map<string, string>();\n\n // Find each route marker followed by a user code block\n const routeMarkerPattern = /\\/\\/ @serverbridge-route:\\s+(\\w+)\\s+(\\/\\S+)/g;\n let match;\n while ((match = routeMarkerPattern.exec(fileContent)) !== null) {\n const method = match[1];\n const path = match[2];\n const key = manifestKey(method, path);\n\n // Find the user-start/user-end block after this marker\n const afterMarker = fileContent.slice(match.index);\n const startIdx = afterMarker.indexOf(USER_START);\n const endIdx = afterMarker.indexOf(USER_END);\n\n if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {\n const userCode = afterMarker.slice(\n startIdx + USER_START.length + 1, // +1 for newline\n endIdx\n );\n // Only save if it differs from the default TODO\n if (!userCode.includes('// TODO: Implement')) {\n result.set(key, userCode);\n }\n }\n }\n\n return result;\n}\n\n/**\n * Determine what changed between old and new route sets.\n */\nexport function diffRoutes(\n oldManifest: Manifest | null,\n newRoutes: RouteDescriptor[]\n): {\n added: RouteDescriptor[];\n removed: ManifestEntry[];\n changed: RouteDescriptor[];\n unchanged: RouteDescriptor[];\n} {\n const oldRoutes = oldManifest?.routes ?? {};\n\n const added: RouteDescriptor[] = [];\n const changed: RouteDescriptor[] = [];\n const unchanged: RouteDescriptor[] = [];\n\n for (const route of newRoutes) {\n const key = manifestKey(route.method, route.path);\n const existing = oldRoutes[key];\n\n if (!existing) {\n added.push(route);\n } else if (existing.hash !== hashRoute(route)) {\n changed.push(route);\n } else {\n unchanged.push(route);\n }\n }\n\n // Find removed routes\n const newKeys = new Set(newRoutes.map(r => manifestKey(r.method, r.path)));\n const removed = Object.values(oldRoutes).filter(\n entry => !newKeys.has(manifestKey(entry.method, entry.path))\n );\n\n return { added, removed, changed, unchanged };\n}\n","import { resolve } from 'node:path';\nimport { scanDirectory } from './scanner/route-scanner.js';\nimport { selectGenerator } from './generators/index.js';\nimport type { GenerateResult, RouteDescriptor, ScanResult } from './types.js';\n\nexport interface ProgrammaticGenerateOptions {\n dir: string;\n framework?: 'express' | 'hono' | 'openapi' | 'django' | 'fastapi';\n output?: string;\n typescript?: boolean;\n include?: string[];\n exclude?: string[];\n ignore?: string[];\n}\n\nexport interface GenerateFullResult {\n scan: ScanResult;\n generated: GenerateResult;\n routes: RouteDescriptor[];\n}\n\n/**\n * Programmatic scan + generate. Scans a directory for routes and generates\n * framework-specific route files. Returns structured results for the caller\n * to write to disk.\n */\nexport async function generate(options: ProgrammaticGenerateOptions): Promise<GenerateFullResult> {\n const cwd = resolve(options.dir);\n const outputDir = resolve(cwd, options.output ?? './server/routes');\n\n const scan = await scanDirectory(cwd, {\n include: options.include,\n exclude: options.exclude,\n ignore: options.ignore,\n });\n\n const generator = selectGenerator(options.framework ?? 'express');\n const generated = generator.generate(scan.routes, {\n outputDir,\n typescript: options.typescript,\n });\n\n return { scan, generated, routes: scan.routes };\n}\n"],"mappings":"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","names":["match","addRoute","lineNumberAt","toCamelCase","PY_ROUTE_MARKER","PY_USER_START","PY_USER_END","toSnakeCase","pythonType","readFile","readFile"]}
@@ -0,0 +1,10 @@
1
+ import type { RouteDescriptor, ConflictWarning } from '../types.js';
2
+ /**
3
+ * Detect conflicts between multiple declarations of the same route.
4
+ * Must be called on the pre-deduplicated route list (before first-wins dedup).
5
+ */
6
+ export declare function detectConflicts(routes: RouteDescriptor[]): ConflictWarning[];
7
+ /**
8
+ * Format conflict warnings for human-readable CLI output.
9
+ */
10
+ export declare function formatConflicts(warnings: ConflictWarning[]): string;
@@ -0,0 +1,12 @@
1
+ import type { RequestBodyField } from '../types.js';
2
+ /**
3
+ * Extract form fields from a <form> block's inner HTML.
4
+ */
5
+ export declare function extractFormFields(formInnerHtml: string): RequestBodyField[];
6
+ /**
7
+ * Find all <form> blocks in HTML content that contain route-implying attributes
8
+ * (hx-post, hx-put, hx-patch, fx-action) and extract their fields.
9
+ *
10
+ * Returns a map from URL path to field list.
11
+ */
12
+ export declare function extractFormBodies(content: string): Map<string, RequestBodyField[]>;
@@ -0,0 +1,9 @@
1
+ import type { RouteDescriptor } from '../types.js';
2
+ interface ExtractionOptions {
3
+ file: string;
4
+ }
5
+ /**
6
+ * Extract route descriptors from htmx (hx-*) and fixi (fx-*) attributes.
7
+ */
8
+ export declare function extractHtmxRoutes(content: string, options: ExtractionOptions): RouteDescriptor[];
9
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { RouteDescriptor } from '../types.js';
2
+ interface ExtractionOptions {
3
+ file: string;
4
+ }
5
+ /**
6
+ * Extract route descriptors from hyperscript attributes in file content.
7
+ */
8
+ export declare function extractHyperscriptRoutes(content: string, options: ExtractionOptions): RouteDescriptor[];
9
+ export {};
@@ -0,0 +1,21 @@
1
+ import type { RouteDescriptor, ScanResult } from '../types.js';
2
+ interface ScanOptions {
3
+ include?: string[];
4
+ exclude?: string[];
5
+ /** URL patterns to ignore */
6
+ ignore?: string[];
7
+ }
8
+ /**
9
+ * Scan a single file's content for routes.
10
+ */
11
+ export declare function scanRoutes(content: string, file: string): RouteDescriptor[];
12
+ /**
13
+ * Scan a directory for all HTML/template files and extract routes.
14
+ */
15
+ export declare function scanDirectory(dir: string, options?: ScanOptions): Promise<ScanResult>;
16
+ /**
17
+ * Check if a URL path matches an ignore pattern.
18
+ * Supports simple wildcards: /external/** matches /external/anything
19
+ */
20
+ export declare function matchIgnorePattern(path: string, pattern: string): boolean;
21
+ export {};
@@ -0,0 +1,123 @@
1
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
2
+ /**
3
+ * A server route extracted from hyperscript/htmx/fixi attributes.
4
+ */
5
+ export interface RouteDescriptor {
6
+ /** URL path pattern, e.g., "/api/users/:id" */
7
+ path: string;
8
+ /** HTTP method */
9
+ method: HttpMethod;
10
+ /** Inferred response format */
11
+ responseFormat: 'json' | 'text' | 'html';
12
+ /** Where this route was discovered */
13
+ source: RouteSource;
14
+ /** Inferred request body shape (for POST/PUT/PATCH) */
15
+ requestBody?: RequestBodyField[];
16
+ /** Path parameters extracted from URL */
17
+ pathParams: string[];
18
+ /** Query parameters extracted from URL (e.g., ?q=hello&limit=10) */
19
+ queryParams?: QueryParam[];
20
+ /** Suggested handler function name */
21
+ handlerName: string;
22
+ /** Convention-engine notes */
23
+ notes: string[];
24
+ }
25
+ export interface RouteSource {
26
+ /** Absolute file path */
27
+ file: string;
28
+ /** Line number (1-indexed) */
29
+ line?: number;
30
+ /** How the route was detected */
31
+ kind: 'fetch' | 'hx-attr' | 'fx-attr';
32
+ /** Original source text */
33
+ raw: string;
34
+ }
35
+ export interface RequestBodyField {
36
+ /** Field name from form input name="" attribute */
37
+ name: string;
38
+ /** Inferred type from input type="" attribute */
39
+ type: 'string' | 'number' | 'boolean' | 'file' | 'unknown';
40
+ /** Whether the field has the required attribute */
41
+ required: boolean;
42
+ }
43
+ export interface QueryParam {
44
+ /** Parameter name from URL query string */
45
+ name: string;
46
+ /** HTTP query params are always strings */
47
+ type: 'string';
48
+ /** Whether the param appears required (true when explicitly present in URL) */
49
+ required: boolean;
50
+ }
51
+ export interface ScanResult {
52
+ /** All extracted routes */
53
+ routes: RouteDescriptor[];
54
+ /** Files that were scanned */
55
+ filesScanned: string[];
56
+ /** Errors during scanning */
57
+ errors: ScanError[];
58
+ /** Route conflicts detected (same method+path, different expectations) */
59
+ conflicts: ConflictWarning[];
60
+ }
61
+ export interface ConflictWarning {
62
+ routeKey: string;
63
+ conflicts: ConflictDetail[];
64
+ sources: RouteSource[];
65
+ }
66
+ export interface ConflictDetail {
67
+ field: 'responseFormat' | 'requestBody' | 'queryParams';
68
+ values: string[];
69
+ message: string;
70
+ }
71
+ export interface ScanError {
72
+ file: string;
73
+ line?: number;
74
+ message: string;
75
+ }
76
+ export interface GeneratorOptions {
77
+ /** Output directory for generated files */
78
+ outputDir: string;
79
+ /** Whether to use TypeScript (default: true) */
80
+ typescript?: boolean;
81
+ /** Overwrite existing user code (default: false) */
82
+ overwrite?: boolean;
83
+ /** Base path prefix to strip from routes */
84
+ basePath?: string;
85
+ }
86
+ export interface GeneratedFile {
87
+ /** Relative path within outputDir */
88
+ path: string;
89
+ /** File content */
90
+ content: string;
91
+ /** Whether this is a new file or regenerated */
92
+ isNew: boolean;
93
+ }
94
+ export interface GenerateResult {
95
+ /** Generated files */
96
+ files: GeneratedFile[];
97
+ /** Routes with preserved user code */
98
+ preserved: string[];
99
+ /** Warnings */
100
+ warnings: string[];
101
+ }
102
+ export interface ServerBridgeConfig {
103
+ /** Target framework */
104
+ framework?: 'express' | 'hono' | 'openapi' | 'django' | 'fastapi';
105
+ /** Output directory */
106
+ output?: string;
107
+ /** TypeScript output */
108
+ typescript?: boolean;
109
+ /** File include patterns */
110
+ include?: string[];
111
+ /** File exclude patterns */
112
+ exclude?: string[];
113
+ /** Base path prefix to strip */
114
+ basePath?: string;
115
+ /** Convention overrides */
116
+ conventions?: {
117
+ defaultResponseFormat?: 'json' | 'html' | 'text';
118
+ fetchMethod?: HttpMethod;
119
+ formMethod?: HttpMethod;
120
+ };
121
+ /** URL patterns to ignore */
122
+ ignore?: string[];
123
+ }
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@hyperfixi/server-bridge",
3
+ "version": "2.0.0",
4
+ "description": "Extract server route contracts from hyperscript/htmx HTML and generate server-side route scaffolding",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "bin": {
10
+ "serverbridge": "dist/cli.cjs"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js",
16
+ "require": "./dist/index.cjs"
17
+ }
18
+ },
19
+ "scripts": {
20
+ "build": "tsup && npm run build:types",
21
+ "build:types": "tsc --emitDeclarationOnly --noEmit false --outDir dist",
22
+ "test": "vitest",
23
+ "test:run": "vitest run",
24
+ "test:check": "vitest run --reporter=dot 2>&1 | tail -5",
25
+ "typecheck": "tsc --noEmit"
26
+ },
27
+ "dependencies": {
28
+ "glob": "^10.0.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^20.0.0",
32
+ "tsup": "^8.0.0",
33
+ "typescript": "^5.0.0",
34
+ "vitest": "^4.0.0"
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "src",
39
+ "LICENSE"
40
+ ],
41
+ "keywords": [
42
+ "hyperscript",
43
+ "htmx",
44
+ "server",
45
+ "routes",
46
+ "codegen",
47
+ "express",
48
+ "hono",
49
+ "serverbridge"
50
+ ],
51
+ "author": "HyperFixi Contributors",
52
+ "license": "MIT",
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "git+https://github.com/codetalcott/hyperfixi.git",
56
+ "directory": "packages/server-bridge"
57
+ },
58
+ "engines": {
59
+ "node": ">=18.0.0"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public"
63
+ }
64
+ }
@@ -0,0 +1,30 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { loadConfig } from '../cli/config.js';
3
+
4
+ // Test the config loading logic (scan/generate commands are tested via integration)
5
+ describe('loadConfig', () => {
6
+ it('returns defaults when no config file exists', async () => {
7
+ const config = await loadConfig('/nonexistent');
8
+ expect(config.framework).toBe('express');
9
+ expect(config.output).toBe('./server/routes');
10
+ expect(config.typescript).toBe(true);
11
+ expect(config.include).toEqual(['**/*.{html,htm}']);
12
+ });
13
+
14
+ it('applies CLI overrides', async () => {
15
+ const config = await loadConfig('/nonexistent', {
16
+ framework: 'hono',
17
+ output: './custom/routes',
18
+ typescript: false,
19
+ });
20
+ expect(config.framework).toBe('hono');
21
+ expect(config.output).toBe('./custom/routes');
22
+ expect(config.typescript).toBe(false);
23
+ });
24
+
25
+ it('preserves default include/exclude when not overridden', async () => {
26
+ const config = await loadConfig('/nonexistent', { framework: 'hono' });
27
+ expect(config.include).toEqual(['**/*.{html,htm}']);
28
+ expect(config.exclude).toEqual(['node_modules/**', 'dist/**']);
29
+ });
30
+ });