shokupan 0.10.4 → 0.11.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 (54) hide show
  1. package/dist/{analyzer-CKLGLFtx.cjs → analyzer-BAhvpNY_.cjs} +2 -7
  2. package/dist/{analyzer-CKLGLFtx.cjs.map → analyzer-BAhvpNY_.cjs.map} +1 -1
  3. package/dist/{analyzer-BqIe1p0R.js → analyzer-CnKnQ5KV.js} +3 -8
  4. package/dist/{analyzer-BqIe1p0R.js.map → analyzer-CnKnQ5KV.js.map} +1 -1
  5. package/dist/{analyzer.impl-D9Yi1Hax.cjs → analyzer.impl-CfpMu4-g.cjs} +586 -40
  6. package/dist/analyzer.impl-CfpMu4-g.cjs.map +1 -0
  7. package/dist/{analyzer.impl-CV6W1Eq7.js → analyzer.impl-DCiqlXI5.js} +586 -40
  8. package/dist/analyzer.impl-DCiqlXI5.js.map +1 -0
  9. package/dist/cli.cjs +206 -18
  10. package/dist/cli.cjs.map +1 -1
  11. package/dist/cli.js +206 -18
  12. package/dist/cli.js.map +1 -1
  13. package/dist/context.d.ts +6 -1
  14. package/dist/index.cjs +2405 -1008
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.js +2402 -1006
  17. package/dist/index.js.map +1 -1
  18. package/dist/plugins/application/api-explorer/static/explorer-client.mjs +423 -30
  19. package/dist/plugins/application/api-explorer/static/style.css +351 -10
  20. package/dist/plugins/application/api-explorer/static/theme.css +7 -2
  21. package/dist/plugins/application/asyncapi/generator.d.ts +4 -0
  22. package/dist/plugins/application/asyncapi/static/asyncapi-client.mjs +154 -22
  23. package/dist/plugins/application/asyncapi/static/style.css +24 -8
  24. package/dist/plugins/application/dashboard/fetch-interceptor.d.ts +107 -0
  25. package/dist/plugins/application/dashboard/metrics-collector.d.ts +38 -2
  26. package/dist/plugins/application/dashboard/plugin.d.ts +44 -1
  27. package/dist/plugins/application/dashboard/static/charts.js +127 -62
  28. package/dist/plugins/application/dashboard/static/client.js +160 -0
  29. package/dist/plugins/application/dashboard/static/graph.mjs +167 -56
  30. package/dist/plugins/application/dashboard/static/reactflow.css +20 -10
  31. package/dist/plugins/application/dashboard/static/registry.js +112 -8
  32. package/dist/plugins/application/dashboard/static/requests.js +868 -58
  33. package/dist/plugins/application/dashboard/static/styles.css +186 -14
  34. package/dist/plugins/application/dashboard/static/tabs.js +44 -9
  35. package/dist/plugins/application/dashboard/static/theme.css +7 -2
  36. package/dist/plugins/application/openapi/analyzer.impl.d.ts +61 -1
  37. package/dist/plugins/application/openapi/openapi.d.ts +3 -0
  38. package/dist/plugins/application/shared/ast-utils.d.ts +7 -0
  39. package/dist/router.d.ts +55 -16
  40. package/dist/shokupan.d.ts +7 -2
  41. package/dist/util/adapter/adapters.d.ts +19 -0
  42. package/dist/util/adapter/filesystem.d.ts +20 -0
  43. package/dist/util/controller-scanner.d.ts +4 -0
  44. package/dist/util/cpu-monitor.d.ts +2 -0
  45. package/dist/util/middleware-tracker.d.ts +10 -0
  46. package/dist/util/types.d.ts +37 -0
  47. package/package.json +5 -5
  48. package/dist/analyzer.impl-CV6W1Eq7.js.map +0 -1
  49. package/dist/analyzer.impl-D9Yi1Hax.cjs.map +0 -1
  50. package/dist/http-server-BEMPIs33.cjs +0 -85
  51. package/dist/http-server-BEMPIs33.cjs.map +0 -1
  52. package/dist/http-server-CCeagTyU.js +0 -68
  53. package/dist/http-server-CCeagTyU.js.map +0 -1
  54. package/dist/plugins/application/dashboard/static/poll.js +0 -146
package/dist/cli.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.cjs","sources":["../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env bun\nimport * as p from '@clack/prompts';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { setTimeout } from 'node:timers/promises';\nimport { analyzeDirectory } from '../plugins/application/openapi/analyzer';\n\nconst templates = {\n controller: (name: string) => `import { Controller, Get, Ctx } from 'shokupan';\nimport { ShokupanContext } from 'shokupan';\n\n@Controller('/${name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}')\nexport class ${name}Controller {\n @Get('/')\n public index(@Ctx() ctx: ShokupanContext) {\n return { message: 'Hello from ${name}Controller' };\n }\n}\n`,\n middleware: (name: string) => `import { ShokupanContext, NextFn } from 'shokupan';\n\n/**\n * ${name} Middleware\n */\nexport const ${name}Middleware = async (ctx: ShokupanContext, next: NextFn) => {\n // Before next\n // console.log('${name} Middleware - Request');\n\n const result = await next();\n\n // After next\n // console.log('${name} Middleware - Response');\n \n return result;\n};\n`,\n plugin: (name: string) => `import { ShokupanRouter } from 'shokupan';\nimport { ShokupanContext } from 'shokupan';\n\nexport interface ${name}Options {\n // Define options here\n}\n\nexport class ${name}Plugin extends ShokupanRouter {\n constructor(private options: ${name}Options = {}) {\n super();\n this.init();\n }\n\n private init() {\n this.get('/', (ctx: ShokupanContext) => {\n return { message: '${name} Plugin Active' };\n });\n }\n}\n`\n};\n\nasync function scaffold() {\n console.clear();\n p.intro(`Shokupan CLI Scaffolder`);\n\n // Check if running in a project root\n if (!fs.existsSync('package.json')) {\n p.note('Warning: No package.json found in current directory. Are you in the project root?');\n }\n\n const project = await p.group(\n {\n type: () => p.select({\n message: 'What do you want to scaffold?',\n options: [\n { value: 'controller', label: 'Controller' },\n { value: 'middleware', label: 'Middleware' },\n { value: 'plugin', label: 'Plugin' },\n ],\n }),\n name: () => p.text({\n message: 'Name (PascalCase, e.g. UserAuth):',\n validate: (value) => {\n if (!value) return 'Name is required';\n if (!/^[A-Z][a-zA-Z0-9]*$/.test(value)) return 'Please use PascalCase';\n return undefined;\n },\n }),\n dir: () => p.text({\n message: 'Output directory (leave empty for default):',\n placeholder: 'src/controllers',\n }),\n },\n {\n onCancel: () => {\n p.cancel('Operation cancelled.');\n process.exit(0);\n },\n }\n );\n\n const type = project.type as keyof typeof templates;\n const name = project.name;\n let dir = project.dir;\n\n if (!dir || dir.trim() === '') {\n switch (type) {\n case 'controller': dir = 'src/controllers'; break;\n case 'middleware': dir = 'src/middleware'; break;\n case 'plugin': dir = 'src/plugins'; break;\n }\n }\n\n // Convert PascalCase to kebab-case for filename\n const kebabName = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();\n const fileName = `${kebabName}.ts`;\n\n const finalPath = path.join(process.cwd(), dir, fileName);\n\n // Ensure directory exists\n if (!fs.existsSync(path.dirname(finalPath))) {\n fs.mkdirSync(path.dirname(finalPath), { recursive: true });\n }\n\n // Check for overwrite\n if (fs.existsSync(finalPath)) {\n const overwrite = await p.confirm({\n message: `File ${finalPath} already exists. Overwrite?`,\n initialValue: false\n });\n\n if (p.isCancel(overwrite) || !overwrite) {\n p.cancel('Operation cancelled.');\n process.exit(0);\n }\n }\n\n const s = p.spinner();\n s.start(`Creating ${type}...`);\n\n await setTimeout(500); // Artificial delay to show spinner\n\n const content = templates[type](name);\n fs.writeFileSync(finalPath, content);\n\n s.stop(`Created ${type}`);\n\n const nextSteps = ` -> ${finalPath}\nMake sure to register it in your main application file if necessary.`;\n\n p.note(nextSteps, 'Next steps');\n\n p.outro(`Problems? Open an issue at https://github.com/dotglitch/shokupan`);\n}\n\nasync function analyze() {\n console.clear();\n p.intro(`Shokupan OpenAPI Analyzer`);\n\n const args = process.argv.slice(2);\n let directory = process.cwd();\n let outputPath = 'openapi.json';\n\n // Parse command line arguments\n // analyze [directory] [--output file.json]\n const analyzeIndex = args.indexOf('analyze');\n if (analyzeIndex !== -1 && args.length > analyzeIndex + 1) {\n const nextArg = args[analyzeIndex + 1];\n if (!nextArg.startsWith('--')) {\n directory = path.resolve(nextArg);\n }\n }\n\n const outputIndex = args.indexOf('--output');\n if (outputIndex !== -1 && args.length > outputIndex + 1) {\n outputPath = args[outputIndex + 1];\n }\n\n // Verify directory exists\n if (!fs.existsSync(directory)) {\n p.cancel(`Directory not found: ${directory}`);\n process.exit(1);\n }\n\n const s = p.spinner();\n s.start(`Analyzing directory: ${directory}`);\n\n try {\n const spec = await analyzeDirectory(directory);\n\n s.stop('Analysis complete');\n\n // Write to file\n const fullOutputPath = path.resolve(outputPath);\n fs.writeFileSync(fullOutputPath, JSON.stringify(spec, null, 2));\n\n p.note(`OpenAPI spec written to: ${fullOutputPath}`, 'Success');\n\n // Show summary\n const pathCount = Object.keys(spec.paths || {}).length;\n p.note(`Found ${pathCount} unique paths`, 'Summary');\n\n p.outro('Done!');\n } catch (error: any) {\n s.stop('Analysis failed');\n p.cancel(`Error: ${error.message}`);\n console.error(error);\n process.exit(1);\n }\n}\n\nasync function main() {\n const args = process.argv.slice(2);\n const command = args[0];\n\n if (command === 'analyze') {\n await analyze();\n } else if (command === 'scaffold' || !command) {\n // Default to scaffold for backwards compatibility\n await scaffold();\n } else {\n console.log('Shokupan CLI');\n console.log('');\n console.log('Commands:');\n console.log(' scaffold (default) - Scaffold controllers, middleware, or plugins');\n console.log(' analyze <directory> - Analyze a Shokupan application and generate OpenAPI spec');\n console.log('');\n console.log('Usage:');\n console.log(' shokupan scaffold');\n console.log(' shokupan analyze <directory> [--output openapi.json]');\n process.exit(0);\n }\n}\n\nmain().catch(console.error);\n"],"names":["p","setTimeout","analyzeDirectory"],"mappings":"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"}
1
+ {"version":3,"file":"cli.cjs","sources":["../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env bun\nimport * as p from '@clack/prompts';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { setTimeout } from 'node:timers/promises';\nimport { OpenAPIAnalyzer } from '../plugins/application/openapi/analyzer';\n\nconst templates = {\n controller: (name: string) => `import { Controller, Get, Ctx } from 'shokupan';\nimport { ShokupanContext } from 'shokupan';\n\n@Controller('/${name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}')\nexport class ${name}Controller {\n @Get('/')\n public index(@Ctx() ctx: ShokupanContext) {\n return { message: 'Hello from ${name}Controller' };\n }\n}\n`,\n middleware: (name: string) => `import { ShokupanContext, NextFn } from 'shokupan';\n\n/**\n * ${name} Middleware\n */\nexport const ${name}Middleware = async (ctx: ShokupanContext, next: NextFn) => {\n // Before next\n // console.log('${name} Middleware - Request');\n\n const result = await next();\n\n // After next\n // console.log('${name} Middleware - Response');\n \n return result;\n};\n`,\n plugin: (name: string) => `import { ShokupanRouter } from 'shokupan';\nimport { ShokupanContext } from 'shokupan';\n\nexport interface ${name}Options {\n // Define options here\n}\n\nexport class ${name}Plugin extends ShokupanRouter {\n constructor(private options: ${name}Options = {}) {\n super();\n this.init();\n }\n\n private init() {\n this.get('/', (ctx: ShokupanContext) => {\n return { message: '${name} Plugin Active' };\n });\n }\n}\n`\n};\n\nasync function scaffold() {\n console.clear();\n p.intro(`Shokupan CLI Scaffolder`);\n\n // Check if running in a project root\n if (!fs.existsSync('package.json')) {\n p.note('Warning: No package.json found in current directory. Are you in the project root?');\n }\n\n const project = await p.group(\n {\n type: () => p.select({\n message: 'What do you want to scaffold?',\n options: [\n { value: 'controller', label: 'Controller' },\n { value: 'middleware', label: 'Middleware' },\n { value: 'plugin', label: 'Plugin' },\n ],\n }),\n name: () => p.text({\n message: 'Name (PascalCase, e.g. UserAuth):',\n validate: (value) => {\n if (!value) return 'Name is required';\n if (!/^[A-Z][a-zA-Z0-9]*$/.test(value)) return 'Please use PascalCase';\n return undefined;\n },\n }),\n dir: () => p.text({\n message: 'Output directory (leave empty for default):',\n placeholder: 'src/controllers',\n }),\n },\n {\n onCancel: () => {\n p.cancel('Operation cancelled.');\n process.exit(0);\n },\n }\n );\n\n const type = project.type as keyof typeof templates;\n const name = project.name;\n let dir = project.dir;\n\n if (!dir || dir.trim() === '') {\n switch (type) {\n case 'controller': dir = 'src/controllers'; break;\n case 'middleware': dir = 'src/middleware'; break;\n case 'plugin': dir = 'src/plugins'; break;\n }\n }\n\n // Convert PascalCase to kebab-case for filename\n const kebabName = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();\n const fileName = `${kebabName}.ts`;\n\n const finalPath = path.join(process.cwd(), dir, fileName);\n\n // Ensure directory exists\n if (!fs.existsSync(path.dirname(finalPath))) {\n fs.mkdirSync(path.dirname(finalPath), { recursive: true });\n }\n\n // Check for overwrite\n if (fs.existsSync(finalPath)) {\n const overwrite = await p.confirm({\n message: `File ${finalPath} already exists. Overwrite?`,\n initialValue: false\n });\n\n if (p.isCancel(overwrite) || !overwrite) {\n p.cancel('Operation cancelled.');\n process.exit(0);\n }\n }\n\n const s = p.spinner();\n s.start(`Creating ${type}...`);\n\n await setTimeout(500); // Artificial delay to show spinner\n\n const content = templates[type](name);\n fs.writeFileSync(finalPath, content);\n\n s.stop(`Created ${type}`);\n\n const nextSteps = ` -> ${finalPath}\nMake sure to register it in your main application file if necessary.`;\n\n p.note(nextSteps, 'Next steps');\n\n p.outro(`Problems? Open an issue at https://github.com/dotglitch/shokupan`);\n}\n\nasync function analyze() {\n await generate(true);\n}\n\nasync function generate(legacyAnalyzeMode = false) {\n if (!legacyAnalyzeMode) {\n console.clear();\n p.intro(`Shokupan Spec Generator`);\n } else {\n console.clear();\n p.intro(`Shokupan OpenAPI Analyzer (Legacy)`);\n }\n\n const args = process.argv.slice(2);\n let directory = process.cwd();\n let openApiPath = 'openapi.json';\n let httpApiPath = 'http-api.json';\n let asyncApiPath = 'asyncapi.json';\n let skipOpenApi = false;\n let skipHttpApi = false;\n let skipAsyncApi = false;\n\n // Parse command line arguments\n const cmdIndex = legacyAnalyzeMode ? args.indexOf('analyze') : args.indexOf('generate');\n\n if (cmdIndex !== -1 && args.length > cmdIndex + 1) {\n const nextArg = args[cmdIndex + 1];\n if (!nextArg.startsWith('--')) {\n directory = path.resolve(nextArg);\n }\n }\n\n // Helper to get arg value\n const getArgValue = (flag: string) => {\n const index = args.indexOf(flag);\n if (index !== -1 && args.length > index + 1) {\n return args[index + 1];\n }\n return null;\n };\n\n const dirArg = getArgValue('--dir');\n if (dirArg) directory = path.resolve(dirArg);\n\n const outArg = getArgValue('--output'); // Legacy support\n if (outArg) openApiPath = outArg;\n\n const openApiArg = getArgValue('--openapi');\n if (openApiArg) openApiPath = openApiArg;\n\n const httpApiArg = getArgValue('--http-api');\n if (httpApiArg) httpApiPath = httpApiArg;\n\n const asyncApiArg = getArgValue('--asyncapi');\n if (asyncApiArg) asyncApiPath = asyncApiArg;\n\n if (args.includes('--skip-openapi')) skipOpenApi = true;\n if (args.includes('--skip-http-api')) skipHttpApi = true;\n if (args.includes('--skip-asyncapi')) skipAsyncApi = true;\n\n if (legacyAnalyzeMode) {\n skipHttpApi = true;\n skipAsyncApi = true;\n }\n\n // Verify directory exists\n if (!fs.existsSync(directory)) {\n p.cancel(`Directory not found: ${directory}`);\n process.exit(1);\n }\n\n const s = p.spinner();\n s.start(`Analyzing directory: ${directory}`);\n\n const warnings: any[] = [];\n\n try {\n const analyzer = new OpenAPIAnalyzer(directory);\n const analysis = await analyzer.analyze();\n\n s.message('Generating specifications...');\n\n // Collect Warnings from Analysis\n const applications = analysis.applications || [];\n\n let pathCount = 0;\n let eventCount = 0;\n\n // Process Applications for Warnings and Counts\n for (const app of applications) {\n for (const route of app.routes) {\n if (['EVENT', 'ON'].includes(route.method.toUpperCase())) {\n eventCount++;\n } else {\n pathCount++;\n }\n\n if (route.path === '__DYNAMIC_EVENT__' || route.path.includes('__DYNAMIC_EVENT__')) {\n warnings.push({\n type: 'dynamic-path',\n message: 'Dynamic path/event detected',\n detail: `Method: ${route.method}`,\n location: route.sourceContext\n });\n }\n\n if (route.emits) {\n for (const emit of route.emits) {\n if (emit.event === '__DYNAMIC_EMIT__') {\n warnings.push({\n type: 'dynamic-emit',\n message: 'Dynamic emit detected',\n detail: `Handler: ${route.handlerName}`,\n location: { file: route.sourceContext?.file, line: emit.location?.startLine }\n });\n }\n }\n }\n }\n }\n\n // 1. Generate Extended OpenAPI (HTTP API)\n let httpApiSpec: any = null;\n if (!skipHttpApi || !skipOpenApi) { // We need it for compliant spec too\n // Use generating function from analyzer if available, or construct basic spec\n // OpenAPIAnalyzer has generateOpenAPISpec\n httpApiSpec = analyzer.generateOpenAPISpec();\n\n // Enrich with middleware registry (manual match from AST)\n const middlewareRegistry: Record<string, any> = {};\n let mwId = 0;\n for (const app of applications) {\n if (app.middleware) {\n for (const mw of app.middleware) {\n const id = `middleware-${mwId++}`;\n middlewareRegistry[id] = { ...mw, id };\n }\n }\n }\n if (Object.keys(middlewareRegistry).length > 0) {\n httpApiSpec[\"x-middleware-registry\"] = middlewareRegistry;\n }\n\n // Filter out EVENT methods from HTTP API spec if analyzer included them\n if (httpApiSpec.paths) {\n for (const pathKey of Object.keys(httpApiSpec.paths)) {\n for (const method of Object.keys(httpApiSpec.paths[pathKey])) {\n if (['event', 'on'].includes(method.toLowerCase())) {\n delete httpApiSpec.paths[pathKey][method];\n }\n }\n if (Object.keys(httpApiSpec.paths[pathKey]).length === 0) {\n delete httpApiSpec.paths[pathKey];\n }\n }\n }\n }\n\n // 2. Generate Compliant OpenAPI\n if (!skipOpenApi && httpApiSpec) {\n const compliantSpec = JSON.parse(JSON.stringify(httpApiSpec));\n\n // Strip extensions\n const stripExtensions = (obj: any) => {\n if (!obj || typeof obj !== 'object') return;\n if (Array.isArray(obj)) {\n obj.forEach(stripExtensions);\n return;\n }\n for (const key of Object.keys(obj)) {\n if (key.startsWith('x-')) {\n delete obj[key];\n } else {\n stripExtensions(obj[key]);\n }\n }\n };\n stripExtensions(compliantSpec);\n\n const fullOutputPath = path.resolve(openApiPath);\n fs.writeFileSync(fullOutputPath, JSON.stringify(compliantSpec, null, 2));\n if (!legacyAnalyzeMode) p.note(`OpenAPI spec written to: ${fullOutputPath}`, 'OpenAPI');\n else p.note(`OpenAPI spec written to: ${fullOutputPath}`, 'Success');\n }\n\n // 3. Save HTTP API\n if (!skipHttpApi && httpApiSpec) {\n const fullOutputPath = path.resolve(httpApiPath);\n fs.writeFileSync(fullOutputPath, JSON.stringify(httpApiSpec, null, 2));\n if (!legacyAnalyzeMode) p.note(`HTTP API spec written to: ${fullOutputPath}`, 'HTTP API');\n }\n\n // 4. Generate AsyncAPI\n if (!skipAsyncApi) {\n const asyncApiSpec: any = {\n asyncapi: \"3.0.0\",\n info: { title: \"Shokupan AsyncAPI\", version: \"1.0.0\" },\n channels: {}\n };\n\n for (const app of applications) {\n for (const route of app.routes) {\n // 1. Subscribe (Event Handlers)\n if (['EVENT', 'ON'].includes(route.method.toUpperCase())) {\n const eventName = route.path;\n // Prevent overwriting\n if (!asyncApiSpec.channels[eventName]) {\n asyncApiSpec.channels[eventName] = {\n publish: { // Client publishes to server\n operationId: `on${eventName.replace(/[^a-zA-Z0-9]/g, '_')}`,\n message: { payload: { type: 'object' } },\n \"x-source-info\": [route.sourceContext]\n }\n };\n } else {\n // Append source info if possible? AsyncAPI 3.0 allows multiple refs?\n // Simplified: just prefer first or merge info (not easy here)\n }\n }\n\n // 2. Publish (Emits)\n if (route.emits) {\n for (const emit of route.emits) {\n const eventName = emit.event;\n if (!asyncApiSpec.channels[eventName]) {\n asyncApiSpec.channels[eventName] = {\n subscribe: { // Client subscribes to server\n operationId: `emit${eventName.replace(/[^a-zA-Z0-9]/g, '_')}`,\n message: { payload: emit.payload || { type: 'object' } }\n }\n };\n }\n }\n }\n }\n }\n\n const fullOutputPath = path.resolve(asyncApiPath);\n fs.writeFileSync(fullOutputPath, JSON.stringify(asyncApiSpec, null, 2));\n if (!legacyAnalyzeMode) p.note(`AsyncAPI spec written to: ${fullOutputPath}`, 'AsyncAPI');\n }\n\n s.stop('Generation complete');\n\n // Show Warnings\n if (warnings.length > 0) {\n p.note(`${warnings.length} warnings detected during generation.`, 'Warnings');\n const groupedArgs = warnings.reduce((acc, w) => {\n if (!acc[w.type]) acc[w.type] = [];\n acc[w.type].push(w);\n return acc;\n }, {} as Record<string, any[]>);\n\n for (const [type, items] of Object.entries(groupedArgs)) {\n const count = (items as any[]).length;\n console.log(`\\n ${type} (${count}):`);\n (items as any[]).slice(0, 5).forEach(w => {\n console.log(` - ${w.message} ${w.detail ? `(${w.detail})` : ''}`);\n if (w.location) console.log(` at ${w.location.file}:${w.location.line}`);\n });\n if (count > 5) console.log(` ... and ${count - 5} more`);\n }\n }\n\n if (!legacyAnalyzeMode) {\n p.note(`Found ${pathCount} paths and ${eventCount} events`, 'Summary');\n } else {\n // Legacy summary kept simple\n // We can't easily get the count from 'spec' variable that was in old code b/c we generate differently now.\n p.note(`Found ${pathCount} unique paths`, 'Summary');\n }\n\n p.outro('Done!');\n } catch (error: any) {\n s.stop('Analysis failed');\n p.cancel(`Error: ${error.message}`);\n console.error(error);\n process.exit(1);\n }\n}\n\nasync function main() {\n const args = process.argv.slice(2);\n const command = args[0];\n\n if (command === 'analyze') {\n await analyze();\n } else if (command === 'generate') {\n await generate(false);\n } else if (command === 'scaffold' || !command) {\n // Default to scaffold for backwards compatibility\n await scaffold();\n } else {\n console.log('Shokupan CLI');\n console.log('');\n console.log('Commands:');\n console.log(' scaffold (default) - Scaffold controllers, middleware, or plugins');\n console.log(' generate - Generate compliant OpenAPI, HTTP API, and AsyncAPI specs');\n console.log(' analyze <dir> - Content analysis (Legacy/OpenAPI only)');\n console.log('');\n console.log('Usage:');\n console.log(' shokupan scaffold');\n console.log(' shokupan generate [--dir <dir>] [--openapi <path>] [--http-api <path>] [--asyncapi <path>]');\n console.log(' shokupan analyze <directory> [--output openapi.json]');\n process.exit(0);\n }\n}\n\nmain().catch(console.error);\n"],"names":["p","setTimeout","analyzer","OpenAPIAnalyzer"],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import * as p from "@clack/prompts";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { setTimeout } from "node:timers/promises";
6
- import { analyzeDirectory } from "./analyzer-BqIe1p0R.js";
6
+ import { OpenAPIAnalyzer } from "./analyzer-CnKnQ5KV.js";
7
7
  const templates = {
8
8
  controller: (name) => `import { Controller, Get, Ctx } from 'shokupan';
9
9
  import { ShokupanContext } from 'shokupan';
@@ -134,21 +134,54 @@ Make sure to register it in your main application file if necessary.`;
134
134
  p.outro(`Problems? Open an issue at https://github.com/dotglitch/shokupan`);
135
135
  }
136
136
  async function analyze() {
137
- console.clear();
138
- p.intro(`Shokupan OpenAPI Analyzer`);
137
+ await generate(true);
138
+ }
139
+ async function generate(legacyAnalyzeMode = false) {
140
+ if (!legacyAnalyzeMode) {
141
+ console.clear();
142
+ p.intro(`Shokupan Spec Generator`);
143
+ } else {
144
+ console.clear();
145
+ p.intro(`Shokupan OpenAPI Analyzer (Legacy)`);
146
+ }
139
147
  const args = process.argv.slice(2);
140
148
  let directory = process.cwd();
141
- let outputPath = "openapi.json";
142
- const analyzeIndex = args.indexOf("analyze");
143
- if (analyzeIndex !== -1 && args.length > analyzeIndex + 1) {
144
- const nextArg = args[analyzeIndex + 1];
149
+ let openApiPath = "openapi.json";
150
+ let httpApiPath = "http-api.json";
151
+ let asyncApiPath = "asyncapi.json";
152
+ let skipOpenApi = false;
153
+ let skipHttpApi = false;
154
+ let skipAsyncApi = false;
155
+ const cmdIndex = legacyAnalyzeMode ? args.indexOf("analyze") : args.indexOf("generate");
156
+ if (cmdIndex !== -1 && args.length > cmdIndex + 1) {
157
+ const nextArg = args[cmdIndex + 1];
145
158
  if (!nextArg.startsWith("--")) {
146
159
  directory = path.resolve(nextArg);
147
160
  }
148
161
  }
149
- const outputIndex = args.indexOf("--output");
150
- if (outputIndex !== -1 && args.length > outputIndex + 1) {
151
- outputPath = args[outputIndex + 1];
162
+ const getArgValue = (flag) => {
163
+ const index = args.indexOf(flag);
164
+ if (index !== -1 && args.length > index + 1) {
165
+ return args[index + 1];
166
+ }
167
+ return null;
168
+ };
169
+ const dirArg = getArgValue("--dir");
170
+ if (dirArg) directory = path.resolve(dirArg);
171
+ const outArg = getArgValue("--output");
172
+ if (outArg) openApiPath = outArg;
173
+ const openApiArg = getArgValue("--openapi");
174
+ if (openApiArg) openApiPath = openApiArg;
175
+ const httpApiArg = getArgValue("--http-api");
176
+ if (httpApiArg) httpApiPath = httpApiArg;
177
+ const asyncApiArg = getArgValue("--asyncapi");
178
+ if (asyncApiArg) asyncApiPath = asyncApiArg;
179
+ if (args.includes("--skip-openapi")) skipOpenApi = true;
180
+ if (args.includes("--skip-http-api")) skipHttpApi = true;
181
+ if (args.includes("--skip-asyncapi")) skipAsyncApi = true;
182
+ if (legacyAnalyzeMode) {
183
+ skipHttpApi = true;
184
+ skipAsyncApi = true;
152
185
  }
153
186
  if (!fs.existsSync(directory)) {
154
187
  p.cancel(`Directory not found: ${directory}`);
@@ -156,14 +189,165 @@ async function analyze() {
156
189
  }
157
190
  const s = p.spinner();
158
191
  s.start(`Analyzing directory: ${directory}`);
192
+ const warnings = [];
159
193
  try {
160
- const spec = await analyzeDirectory(directory);
161
- s.stop("Analysis complete");
162
- const fullOutputPath = path.resolve(outputPath);
163
- fs.writeFileSync(fullOutputPath, JSON.stringify(spec, null, 2));
164
- p.note(`OpenAPI spec written to: ${fullOutputPath}`, "Success");
165
- const pathCount = Object.keys(spec.paths || {}).length;
166
- p.note(`Found ${pathCount} unique paths`, "Summary");
194
+ const analyzer = new OpenAPIAnalyzer(directory);
195
+ const analysis = await analyzer.analyze();
196
+ s.message("Generating specifications...");
197
+ const applications = analysis.applications || [];
198
+ let pathCount = 0;
199
+ let eventCount = 0;
200
+ for (const app of applications) {
201
+ for (const route of app.routes) {
202
+ if (["EVENT", "ON"].includes(route.method.toUpperCase())) {
203
+ eventCount++;
204
+ } else {
205
+ pathCount++;
206
+ }
207
+ if (route.path === "__DYNAMIC_EVENT__" || route.path.includes("__DYNAMIC_EVENT__")) {
208
+ warnings.push({
209
+ type: "dynamic-path",
210
+ message: "Dynamic path/event detected",
211
+ detail: `Method: ${route.method}`,
212
+ location: route.sourceContext
213
+ });
214
+ }
215
+ if (route.emits) {
216
+ for (const emit of route.emits) {
217
+ if (emit.event === "__DYNAMIC_EMIT__") {
218
+ warnings.push({
219
+ type: "dynamic-emit",
220
+ message: "Dynamic emit detected",
221
+ detail: `Handler: ${route.handlerName}`,
222
+ location: { file: route.sourceContext?.file, line: emit.location?.startLine }
223
+ });
224
+ }
225
+ }
226
+ }
227
+ }
228
+ }
229
+ let httpApiSpec = null;
230
+ if (!skipHttpApi || !skipOpenApi) {
231
+ httpApiSpec = analyzer.generateOpenAPISpec();
232
+ const middlewareRegistry = {};
233
+ let mwId = 0;
234
+ for (const app of applications) {
235
+ if (app.middleware) {
236
+ for (const mw of app.middleware) {
237
+ const id = `middleware-${mwId++}`;
238
+ middlewareRegistry[id] = { ...mw, id };
239
+ }
240
+ }
241
+ }
242
+ if (Object.keys(middlewareRegistry).length > 0) {
243
+ httpApiSpec["x-middleware-registry"] = middlewareRegistry;
244
+ }
245
+ if (httpApiSpec.paths) {
246
+ for (const pathKey of Object.keys(httpApiSpec.paths)) {
247
+ for (const method of Object.keys(httpApiSpec.paths[pathKey])) {
248
+ if (["event", "on"].includes(method.toLowerCase())) {
249
+ delete httpApiSpec.paths[pathKey][method];
250
+ }
251
+ }
252
+ if (Object.keys(httpApiSpec.paths[pathKey]).length === 0) {
253
+ delete httpApiSpec.paths[pathKey];
254
+ }
255
+ }
256
+ }
257
+ }
258
+ if (!skipOpenApi && httpApiSpec) {
259
+ const compliantSpec = JSON.parse(JSON.stringify(httpApiSpec));
260
+ const stripExtensions = (obj) => {
261
+ if (!obj || typeof obj !== "object") return;
262
+ if (Array.isArray(obj)) {
263
+ obj.forEach(stripExtensions);
264
+ return;
265
+ }
266
+ for (const key of Object.keys(obj)) {
267
+ if (key.startsWith("x-")) {
268
+ delete obj[key];
269
+ } else {
270
+ stripExtensions(obj[key]);
271
+ }
272
+ }
273
+ };
274
+ stripExtensions(compliantSpec);
275
+ const fullOutputPath = path.resolve(openApiPath);
276
+ fs.writeFileSync(fullOutputPath, JSON.stringify(compliantSpec, null, 2));
277
+ if (!legacyAnalyzeMode) p.note(`OpenAPI spec written to: ${fullOutputPath}`, "OpenAPI");
278
+ else p.note(`OpenAPI spec written to: ${fullOutputPath}`, "Success");
279
+ }
280
+ if (!skipHttpApi && httpApiSpec) {
281
+ const fullOutputPath = path.resolve(httpApiPath);
282
+ fs.writeFileSync(fullOutputPath, JSON.stringify(httpApiSpec, null, 2));
283
+ if (!legacyAnalyzeMode) p.note(`HTTP API spec written to: ${fullOutputPath}`, "HTTP API");
284
+ }
285
+ if (!skipAsyncApi) {
286
+ const asyncApiSpec = {
287
+ asyncapi: "3.0.0",
288
+ info: { title: "Shokupan AsyncAPI", version: "1.0.0" },
289
+ channels: {}
290
+ };
291
+ for (const app of applications) {
292
+ for (const route of app.routes) {
293
+ if (["EVENT", "ON"].includes(route.method.toUpperCase())) {
294
+ const eventName = route.path;
295
+ if (!asyncApiSpec.channels[eventName]) {
296
+ asyncApiSpec.channels[eventName] = {
297
+ publish: {
298
+ // Client publishes to server
299
+ operationId: `on${eventName.replace(/[^a-zA-Z0-9]/g, "_")}`,
300
+ message: { payload: { type: "object" } },
301
+ "x-source-info": [route.sourceContext]
302
+ }
303
+ };
304
+ } else {
305
+ }
306
+ }
307
+ if (route.emits) {
308
+ for (const emit of route.emits) {
309
+ const eventName = emit.event;
310
+ if (!asyncApiSpec.channels[eventName]) {
311
+ asyncApiSpec.channels[eventName] = {
312
+ subscribe: {
313
+ // Client subscribes to server
314
+ operationId: `emit${eventName.replace(/[^a-zA-Z0-9]/g, "_")}`,
315
+ message: { payload: emit.payload || { type: "object" } }
316
+ }
317
+ };
318
+ }
319
+ }
320
+ }
321
+ }
322
+ }
323
+ const fullOutputPath = path.resolve(asyncApiPath);
324
+ fs.writeFileSync(fullOutputPath, JSON.stringify(asyncApiSpec, null, 2));
325
+ if (!legacyAnalyzeMode) p.note(`AsyncAPI spec written to: ${fullOutputPath}`, "AsyncAPI");
326
+ }
327
+ s.stop("Generation complete");
328
+ if (warnings.length > 0) {
329
+ p.note(`${warnings.length} warnings detected during generation.`, "Warnings");
330
+ const groupedArgs = warnings.reduce((acc, w) => {
331
+ if (!acc[w.type]) acc[w.type] = [];
332
+ acc[w.type].push(w);
333
+ return acc;
334
+ }, {});
335
+ for (const [type, items] of Object.entries(groupedArgs)) {
336
+ const count = items.length;
337
+ console.log(`
338
+ ${type} (${count}):`);
339
+ items.slice(0, 5).forEach((w) => {
340
+ console.log(` - ${w.message} ${w.detail ? `(${w.detail})` : ""}`);
341
+ if (w.location) console.log(` at ${w.location.file}:${w.location.line}`);
342
+ });
343
+ if (count > 5) console.log(` ... and ${count - 5} more`);
344
+ }
345
+ }
346
+ if (!legacyAnalyzeMode) {
347
+ p.note(`Found ${pathCount} paths and ${eventCount} events`, "Summary");
348
+ } else {
349
+ p.note(`Found ${pathCount} unique paths`, "Summary");
350
+ }
167
351
  p.outro("Done!");
168
352
  } catch (error) {
169
353
  s.stop("Analysis failed");
@@ -177,6 +361,8 @@ async function main() {
177
361
  const command = args[0];
178
362
  if (command === "analyze") {
179
363
  await analyze();
364
+ } else if (command === "generate") {
365
+ await generate(false);
180
366
  } else if (command === "scaffold" || !command) {
181
367
  await scaffold();
182
368
  } else {
@@ -184,10 +370,12 @@ async function main() {
184
370
  console.log("");
185
371
  console.log("Commands:");
186
372
  console.log(" scaffold (default) - Scaffold controllers, middleware, or plugins");
187
- console.log(" analyze <directory> - Analyze a Shokupan application and generate OpenAPI spec");
373
+ console.log(" generate - Generate compliant OpenAPI, HTTP API, and AsyncAPI specs");
374
+ console.log(" analyze <dir> - Content analysis (Legacy/OpenAPI only)");
188
375
  console.log("");
189
376
  console.log("Usage:");
190
377
  console.log(" shokupan scaffold");
378
+ console.log(" shokupan generate [--dir <dir>] [--openapi <path>] [--http-api <path>] [--asyncapi <path>]");
191
379
  console.log(" shokupan analyze <directory> [--output openapi.json]");
192
380
  process.exit(0);
193
381
  }
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sources":["../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env bun\nimport * as p from '@clack/prompts';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { setTimeout } from 'node:timers/promises';\nimport { analyzeDirectory } from '../plugins/application/openapi/analyzer';\n\nconst templates = {\n controller: (name: string) => `import { Controller, Get, Ctx } from 'shokupan';\nimport { ShokupanContext } from 'shokupan';\n\n@Controller('/${name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}')\nexport class ${name}Controller {\n @Get('/')\n public index(@Ctx() ctx: ShokupanContext) {\n return { message: 'Hello from ${name}Controller' };\n }\n}\n`,\n middleware: (name: string) => `import { ShokupanContext, NextFn } from 'shokupan';\n\n/**\n * ${name} Middleware\n */\nexport const ${name}Middleware = async (ctx: ShokupanContext, next: NextFn) => {\n // Before next\n // console.log('${name} Middleware - Request');\n\n const result = await next();\n\n // After next\n // console.log('${name} Middleware - Response');\n \n return result;\n};\n`,\n plugin: (name: string) => `import { ShokupanRouter } from 'shokupan';\nimport { ShokupanContext } from 'shokupan';\n\nexport interface ${name}Options {\n // Define options here\n}\n\nexport class ${name}Plugin extends ShokupanRouter {\n constructor(private options: ${name}Options = {}) {\n super();\n this.init();\n }\n\n private init() {\n this.get('/', (ctx: ShokupanContext) => {\n return { message: '${name} Plugin Active' };\n });\n }\n}\n`\n};\n\nasync function scaffold() {\n console.clear();\n p.intro(`Shokupan CLI Scaffolder`);\n\n // Check if running in a project root\n if (!fs.existsSync('package.json')) {\n p.note('Warning: No package.json found in current directory. Are you in the project root?');\n }\n\n const project = await p.group(\n {\n type: () => p.select({\n message: 'What do you want to scaffold?',\n options: [\n { value: 'controller', label: 'Controller' },\n { value: 'middleware', label: 'Middleware' },\n { value: 'plugin', label: 'Plugin' },\n ],\n }),\n name: () => p.text({\n message: 'Name (PascalCase, e.g. UserAuth):',\n validate: (value) => {\n if (!value) return 'Name is required';\n if (!/^[A-Z][a-zA-Z0-9]*$/.test(value)) return 'Please use PascalCase';\n return undefined;\n },\n }),\n dir: () => p.text({\n message: 'Output directory (leave empty for default):',\n placeholder: 'src/controllers',\n }),\n },\n {\n onCancel: () => {\n p.cancel('Operation cancelled.');\n process.exit(0);\n },\n }\n );\n\n const type = project.type as keyof typeof templates;\n const name = project.name;\n let dir = project.dir;\n\n if (!dir || dir.trim() === '') {\n switch (type) {\n case 'controller': dir = 'src/controllers'; break;\n case 'middleware': dir = 'src/middleware'; break;\n case 'plugin': dir = 'src/plugins'; break;\n }\n }\n\n // Convert PascalCase to kebab-case for filename\n const kebabName = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();\n const fileName = `${kebabName}.ts`;\n\n const finalPath = path.join(process.cwd(), dir, fileName);\n\n // Ensure directory exists\n if (!fs.existsSync(path.dirname(finalPath))) {\n fs.mkdirSync(path.dirname(finalPath), { recursive: true });\n }\n\n // Check for overwrite\n if (fs.existsSync(finalPath)) {\n const overwrite = await p.confirm({\n message: `File ${finalPath} already exists. Overwrite?`,\n initialValue: false\n });\n\n if (p.isCancel(overwrite) || !overwrite) {\n p.cancel('Operation cancelled.');\n process.exit(0);\n }\n }\n\n const s = p.spinner();\n s.start(`Creating ${type}...`);\n\n await setTimeout(500); // Artificial delay to show spinner\n\n const content = templates[type](name);\n fs.writeFileSync(finalPath, content);\n\n s.stop(`Created ${type}`);\n\n const nextSteps = ` -> ${finalPath}\nMake sure to register it in your main application file if necessary.`;\n\n p.note(nextSteps, 'Next steps');\n\n p.outro(`Problems? Open an issue at https://github.com/dotglitch/shokupan`);\n}\n\nasync function analyze() {\n console.clear();\n p.intro(`Shokupan OpenAPI Analyzer`);\n\n const args = process.argv.slice(2);\n let directory = process.cwd();\n let outputPath = 'openapi.json';\n\n // Parse command line arguments\n // analyze [directory] [--output file.json]\n const analyzeIndex = args.indexOf('analyze');\n if (analyzeIndex !== -1 && args.length > analyzeIndex + 1) {\n const nextArg = args[analyzeIndex + 1];\n if (!nextArg.startsWith('--')) {\n directory = path.resolve(nextArg);\n }\n }\n\n const outputIndex = args.indexOf('--output');\n if (outputIndex !== -1 && args.length > outputIndex + 1) {\n outputPath = args[outputIndex + 1];\n }\n\n // Verify directory exists\n if (!fs.existsSync(directory)) {\n p.cancel(`Directory not found: ${directory}`);\n process.exit(1);\n }\n\n const s = p.spinner();\n s.start(`Analyzing directory: ${directory}`);\n\n try {\n const spec = await analyzeDirectory(directory);\n\n s.stop('Analysis complete');\n\n // Write to file\n const fullOutputPath = path.resolve(outputPath);\n fs.writeFileSync(fullOutputPath, JSON.stringify(spec, null, 2));\n\n p.note(`OpenAPI spec written to: ${fullOutputPath}`, 'Success');\n\n // Show summary\n const pathCount = Object.keys(spec.paths || {}).length;\n p.note(`Found ${pathCount} unique paths`, 'Summary');\n\n p.outro('Done!');\n } catch (error: any) {\n s.stop('Analysis failed');\n p.cancel(`Error: ${error.message}`);\n console.error(error);\n process.exit(1);\n }\n}\n\nasync function main() {\n const args = process.argv.slice(2);\n const command = args[0];\n\n if (command === 'analyze') {\n await analyze();\n } else if (command === 'scaffold' || !command) {\n // Default to scaffold for backwards compatibility\n await scaffold();\n } else {\n console.log('Shokupan CLI');\n console.log('');\n console.log('Commands:');\n console.log(' scaffold (default) - Scaffold controllers, middleware, or plugins');\n console.log(' analyze <directory> - Analyze a Shokupan application and generate OpenAPI spec');\n console.log('');\n console.log('Usage:');\n console.log(' shokupan scaffold');\n console.log(' shokupan analyze <directory> [--output openapi.json]');\n process.exit(0);\n }\n}\n\nmain().catch(console.error);\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sources":["../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env bun\nimport * as p from '@clack/prompts';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { setTimeout } from 'node:timers/promises';\nimport { OpenAPIAnalyzer } from '../plugins/application/openapi/analyzer';\n\nconst templates = {\n controller: (name: string) => `import { Controller, Get, Ctx } from 'shokupan';\nimport { ShokupanContext } from 'shokupan';\n\n@Controller('/${name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}')\nexport class ${name}Controller {\n @Get('/')\n public index(@Ctx() ctx: ShokupanContext) {\n return { message: 'Hello from ${name}Controller' };\n }\n}\n`,\n middleware: (name: string) => `import { ShokupanContext, NextFn } from 'shokupan';\n\n/**\n * ${name} Middleware\n */\nexport const ${name}Middleware = async (ctx: ShokupanContext, next: NextFn) => {\n // Before next\n // console.log('${name} Middleware - Request');\n\n const result = await next();\n\n // After next\n // console.log('${name} Middleware - Response');\n \n return result;\n};\n`,\n plugin: (name: string) => `import { ShokupanRouter } from 'shokupan';\nimport { ShokupanContext } from 'shokupan';\n\nexport interface ${name}Options {\n // Define options here\n}\n\nexport class ${name}Plugin extends ShokupanRouter {\n constructor(private options: ${name}Options = {}) {\n super();\n this.init();\n }\n\n private init() {\n this.get('/', (ctx: ShokupanContext) => {\n return { message: '${name} Plugin Active' };\n });\n }\n}\n`\n};\n\nasync function scaffold() {\n console.clear();\n p.intro(`Shokupan CLI Scaffolder`);\n\n // Check if running in a project root\n if (!fs.existsSync('package.json')) {\n p.note('Warning: No package.json found in current directory. Are you in the project root?');\n }\n\n const project = await p.group(\n {\n type: () => p.select({\n message: 'What do you want to scaffold?',\n options: [\n { value: 'controller', label: 'Controller' },\n { value: 'middleware', label: 'Middleware' },\n { value: 'plugin', label: 'Plugin' },\n ],\n }),\n name: () => p.text({\n message: 'Name (PascalCase, e.g. UserAuth):',\n validate: (value) => {\n if (!value) return 'Name is required';\n if (!/^[A-Z][a-zA-Z0-9]*$/.test(value)) return 'Please use PascalCase';\n return undefined;\n },\n }),\n dir: () => p.text({\n message: 'Output directory (leave empty for default):',\n placeholder: 'src/controllers',\n }),\n },\n {\n onCancel: () => {\n p.cancel('Operation cancelled.');\n process.exit(0);\n },\n }\n );\n\n const type = project.type as keyof typeof templates;\n const name = project.name;\n let dir = project.dir;\n\n if (!dir || dir.trim() === '') {\n switch (type) {\n case 'controller': dir = 'src/controllers'; break;\n case 'middleware': dir = 'src/middleware'; break;\n case 'plugin': dir = 'src/plugins'; break;\n }\n }\n\n // Convert PascalCase to kebab-case for filename\n const kebabName = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();\n const fileName = `${kebabName}.ts`;\n\n const finalPath = path.join(process.cwd(), dir, fileName);\n\n // Ensure directory exists\n if (!fs.existsSync(path.dirname(finalPath))) {\n fs.mkdirSync(path.dirname(finalPath), { recursive: true });\n }\n\n // Check for overwrite\n if (fs.existsSync(finalPath)) {\n const overwrite = await p.confirm({\n message: `File ${finalPath} already exists. Overwrite?`,\n initialValue: false\n });\n\n if (p.isCancel(overwrite) || !overwrite) {\n p.cancel('Operation cancelled.');\n process.exit(0);\n }\n }\n\n const s = p.spinner();\n s.start(`Creating ${type}...`);\n\n await setTimeout(500); // Artificial delay to show spinner\n\n const content = templates[type](name);\n fs.writeFileSync(finalPath, content);\n\n s.stop(`Created ${type}`);\n\n const nextSteps = ` -> ${finalPath}\nMake sure to register it in your main application file if necessary.`;\n\n p.note(nextSteps, 'Next steps');\n\n p.outro(`Problems? Open an issue at https://github.com/dotglitch/shokupan`);\n}\n\nasync function analyze() {\n await generate(true);\n}\n\nasync function generate(legacyAnalyzeMode = false) {\n if (!legacyAnalyzeMode) {\n console.clear();\n p.intro(`Shokupan Spec Generator`);\n } else {\n console.clear();\n p.intro(`Shokupan OpenAPI Analyzer (Legacy)`);\n }\n\n const args = process.argv.slice(2);\n let directory = process.cwd();\n let openApiPath = 'openapi.json';\n let httpApiPath = 'http-api.json';\n let asyncApiPath = 'asyncapi.json';\n let skipOpenApi = false;\n let skipHttpApi = false;\n let skipAsyncApi = false;\n\n // Parse command line arguments\n const cmdIndex = legacyAnalyzeMode ? args.indexOf('analyze') : args.indexOf('generate');\n\n if (cmdIndex !== -1 && args.length > cmdIndex + 1) {\n const nextArg = args[cmdIndex + 1];\n if (!nextArg.startsWith('--')) {\n directory = path.resolve(nextArg);\n }\n }\n\n // Helper to get arg value\n const getArgValue = (flag: string) => {\n const index = args.indexOf(flag);\n if (index !== -1 && args.length > index + 1) {\n return args[index + 1];\n }\n return null;\n };\n\n const dirArg = getArgValue('--dir');\n if (dirArg) directory = path.resolve(dirArg);\n\n const outArg = getArgValue('--output'); // Legacy support\n if (outArg) openApiPath = outArg;\n\n const openApiArg = getArgValue('--openapi');\n if (openApiArg) openApiPath = openApiArg;\n\n const httpApiArg = getArgValue('--http-api');\n if (httpApiArg) httpApiPath = httpApiArg;\n\n const asyncApiArg = getArgValue('--asyncapi');\n if (asyncApiArg) asyncApiPath = asyncApiArg;\n\n if (args.includes('--skip-openapi')) skipOpenApi = true;\n if (args.includes('--skip-http-api')) skipHttpApi = true;\n if (args.includes('--skip-asyncapi')) skipAsyncApi = true;\n\n if (legacyAnalyzeMode) {\n skipHttpApi = true;\n skipAsyncApi = true;\n }\n\n // Verify directory exists\n if (!fs.existsSync(directory)) {\n p.cancel(`Directory not found: ${directory}`);\n process.exit(1);\n }\n\n const s = p.spinner();\n s.start(`Analyzing directory: ${directory}`);\n\n const warnings: any[] = [];\n\n try {\n const analyzer = new OpenAPIAnalyzer(directory);\n const analysis = await analyzer.analyze();\n\n s.message('Generating specifications...');\n\n // Collect Warnings from Analysis\n const applications = analysis.applications || [];\n\n let pathCount = 0;\n let eventCount = 0;\n\n // Process Applications for Warnings and Counts\n for (const app of applications) {\n for (const route of app.routes) {\n if (['EVENT', 'ON'].includes(route.method.toUpperCase())) {\n eventCount++;\n } else {\n pathCount++;\n }\n\n if (route.path === '__DYNAMIC_EVENT__' || route.path.includes('__DYNAMIC_EVENT__')) {\n warnings.push({\n type: 'dynamic-path',\n message: 'Dynamic path/event detected',\n detail: `Method: ${route.method}`,\n location: route.sourceContext\n });\n }\n\n if (route.emits) {\n for (const emit of route.emits) {\n if (emit.event === '__DYNAMIC_EMIT__') {\n warnings.push({\n type: 'dynamic-emit',\n message: 'Dynamic emit detected',\n detail: `Handler: ${route.handlerName}`,\n location: { file: route.sourceContext?.file, line: emit.location?.startLine }\n });\n }\n }\n }\n }\n }\n\n // 1. Generate Extended OpenAPI (HTTP API)\n let httpApiSpec: any = null;\n if (!skipHttpApi || !skipOpenApi) { // We need it for compliant spec too\n // Use generating function from analyzer if available, or construct basic spec\n // OpenAPIAnalyzer has generateOpenAPISpec\n httpApiSpec = analyzer.generateOpenAPISpec();\n\n // Enrich with middleware registry (manual match from AST)\n const middlewareRegistry: Record<string, any> = {};\n let mwId = 0;\n for (const app of applications) {\n if (app.middleware) {\n for (const mw of app.middleware) {\n const id = `middleware-${mwId++}`;\n middlewareRegistry[id] = { ...mw, id };\n }\n }\n }\n if (Object.keys(middlewareRegistry).length > 0) {\n httpApiSpec[\"x-middleware-registry\"] = middlewareRegistry;\n }\n\n // Filter out EVENT methods from HTTP API spec if analyzer included them\n if (httpApiSpec.paths) {\n for (const pathKey of Object.keys(httpApiSpec.paths)) {\n for (const method of Object.keys(httpApiSpec.paths[pathKey])) {\n if (['event', 'on'].includes(method.toLowerCase())) {\n delete httpApiSpec.paths[pathKey][method];\n }\n }\n if (Object.keys(httpApiSpec.paths[pathKey]).length === 0) {\n delete httpApiSpec.paths[pathKey];\n }\n }\n }\n }\n\n // 2. Generate Compliant OpenAPI\n if (!skipOpenApi && httpApiSpec) {\n const compliantSpec = JSON.parse(JSON.stringify(httpApiSpec));\n\n // Strip extensions\n const stripExtensions = (obj: any) => {\n if (!obj || typeof obj !== 'object') return;\n if (Array.isArray(obj)) {\n obj.forEach(stripExtensions);\n return;\n }\n for (const key of Object.keys(obj)) {\n if (key.startsWith('x-')) {\n delete obj[key];\n } else {\n stripExtensions(obj[key]);\n }\n }\n };\n stripExtensions(compliantSpec);\n\n const fullOutputPath = path.resolve(openApiPath);\n fs.writeFileSync(fullOutputPath, JSON.stringify(compliantSpec, null, 2));\n if (!legacyAnalyzeMode) p.note(`OpenAPI spec written to: ${fullOutputPath}`, 'OpenAPI');\n else p.note(`OpenAPI spec written to: ${fullOutputPath}`, 'Success');\n }\n\n // 3. Save HTTP API\n if (!skipHttpApi && httpApiSpec) {\n const fullOutputPath = path.resolve(httpApiPath);\n fs.writeFileSync(fullOutputPath, JSON.stringify(httpApiSpec, null, 2));\n if (!legacyAnalyzeMode) p.note(`HTTP API spec written to: ${fullOutputPath}`, 'HTTP API');\n }\n\n // 4. Generate AsyncAPI\n if (!skipAsyncApi) {\n const asyncApiSpec: any = {\n asyncapi: \"3.0.0\",\n info: { title: \"Shokupan AsyncAPI\", version: \"1.0.0\" },\n channels: {}\n };\n\n for (const app of applications) {\n for (const route of app.routes) {\n // 1. Subscribe (Event Handlers)\n if (['EVENT', 'ON'].includes(route.method.toUpperCase())) {\n const eventName = route.path;\n // Prevent overwriting\n if (!asyncApiSpec.channels[eventName]) {\n asyncApiSpec.channels[eventName] = {\n publish: { // Client publishes to server\n operationId: `on${eventName.replace(/[^a-zA-Z0-9]/g, '_')}`,\n message: { payload: { type: 'object' } },\n \"x-source-info\": [route.sourceContext]\n }\n };\n } else {\n // Append source info if possible? AsyncAPI 3.0 allows multiple refs?\n // Simplified: just prefer first or merge info (not easy here)\n }\n }\n\n // 2. Publish (Emits)\n if (route.emits) {\n for (const emit of route.emits) {\n const eventName = emit.event;\n if (!asyncApiSpec.channels[eventName]) {\n asyncApiSpec.channels[eventName] = {\n subscribe: { // Client subscribes to server\n operationId: `emit${eventName.replace(/[^a-zA-Z0-9]/g, '_')}`,\n message: { payload: emit.payload || { type: 'object' } }\n }\n };\n }\n }\n }\n }\n }\n\n const fullOutputPath = path.resolve(asyncApiPath);\n fs.writeFileSync(fullOutputPath, JSON.stringify(asyncApiSpec, null, 2));\n if (!legacyAnalyzeMode) p.note(`AsyncAPI spec written to: ${fullOutputPath}`, 'AsyncAPI');\n }\n\n s.stop('Generation complete');\n\n // Show Warnings\n if (warnings.length > 0) {\n p.note(`${warnings.length} warnings detected during generation.`, 'Warnings');\n const groupedArgs = warnings.reduce((acc, w) => {\n if (!acc[w.type]) acc[w.type] = [];\n acc[w.type].push(w);\n return acc;\n }, {} as Record<string, any[]>);\n\n for (const [type, items] of Object.entries(groupedArgs)) {\n const count = (items as any[]).length;\n console.log(`\\n ${type} (${count}):`);\n (items as any[]).slice(0, 5).forEach(w => {\n console.log(` - ${w.message} ${w.detail ? `(${w.detail})` : ''}`);\n if (w.location) console.log(` at ${w.location.file}:${w.location.line}`);\n });\n if (count > 5) console.log(` ... and ${count - 5} more`);\n }\n }\n\n if (!legacyAnalyzeMode) {\n p.note(`Found ${pathCount} paths and ${eventCount} events`, 'Summary');\n } else {\n // Legacy summary kept simple\n // We can't easily get the count from 'spec' variable that was in old code b/c we generate differently now.\n p.note(`Found ${pathCount} unique paths`, 'Summary');\n }\n\n p.outro('Done!');\n } catch (error: any) {\n s.stop('Analysis failed');\n p.cancel(`Error: ${error.message}`);\n console.error(error);\n process.exit(1);\n }\n}\n\nasync function main() {\n const args = process.argv.slice(2);\n const command = args[0];\n\n if (command === 'analyze') {\n await analyze();\n } else if (command === 'generate') {\n await generate(false);\n } else if (command === 'scaffold' || !command) {\n // Default to scaffold for backwards compatibility\n await scaffold();\n } else {\n console.log('Shokupan CLI');\n console.log('');\n console.log('Commands:');\n console.log(' scaffold (default) - Scaffold controllers, middleware, or plugins');\n console.log(' generate - Generate compliant OpenAPI, HTTP API, and AsyncAPI specs');\n console.log(' analyze <dir> - Content analysis (Legacy/OpenAPI only)');\n console.log('');\n console.log('Usage:');\n console.log(' shokupan scaffold');\n console.log(' shokupan generate [--dir <dir>] [--openapi <path>] [--http-api <path>] [--asyncapi <path>]');\n console.log(' shokupan analyze <directory> [--output openapi.json]');\n process.exit(0);\n }\n}\n\nmain().catch(console.error);\n"],"names":[],"mappings":";;;;;;AAOA,MAAM,YAAY;AAAA,EACd,YAAY,CAAC,SAAiB;AAAA;AAAA;AAAA,gBAGlB,KAAK,QAAQ,mBAAmB,OAAO,EAAE,aAAa;AAAA,eACvD,IAAI;AAAA;AAAA;AAAA,wCAGqB,IAAI;AAAA;AAAA;AAAA;AAAA,EAIxC,YAAY,CAAC,SAAiB;AAAA;AAAA;AAAA,KAG7B,IAAI;AAAA;AAAA,eAEM,IAAI;AAAA;AAAA,sBAEG,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA,sBAKJ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA,EAKtB,QAAQ,CAAC,SAAiB;AAAA;AAAA;AAAA,mBAGX,IAAI;AAAA;AAAA;AAAA;AAAA,eAIR,IAAI;AAAA,mCACgB,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iCAON,IAAI;AAAA;AAAA;AAAA;AAAA;AAKrC;AAEA,eAAe,WAAW;AACtB,UAAQ,MAAA;AACR,IAAE,MAAM,yBAAyB;AAGjC,MAAI,CAAC,GAAG,WAAW,cAAc,GAAG;AAChC,MAAE,KAAK,mFAAmF;AAAA,EAC9F;AAEA,QAAM,UAAU,MAAM,EAAE;AAAA,IACpB;AAAA,MACI,MAAM,MAAM,EAAE,OAAO;AAAA,QACjB,SAAS;AAAA,QACT,SAAS;AAAA,UACL,EAAE,OAAO,cAAc,OAAO,aAAA;AAAA,UAC9B,EAAE,OAAO,cAAc,OAAO,aAAA;AAAA,UAC9B,EAAE,OAAO,UAAU,OAAO,SAAA;AAAA,QAAS;AAAA,MACvC,CACH;AAAA,MACD,MAAM,MAAM,EAAE,KAAK;AAAA,QACf,SAAS;AAAA,QACT,UAAU,CAAC,UAAU;AACjB,cAAI,CAAC,MAAO,QAAO;AACnB,cAAI,CAAC,sBAAsB,KAAK,KAAK,EAAG,QAAO;AAC/C,iBAAO;AAAA,QACX;AAAA,MAAA,CACH;AAAA,MACD,KAAK,MAAM,EAAE,KAAK;AAAA,QACd,SAAS;AAAA,QACT,aAAa;AAAA,MAAA,CAChB;AAAA,IAAA;AAAA,IAEL;AAAA,MACI,UAAU,MAAM;AACZ,UAAE,OAAO,sBAAsB;AAC/B,gBAAQ,KAAK,CAAC;AAAA,MAClB;AAAA,IAAA;AAAA,EACJ;AAGJ,QAAM,OAAO,QAAQ;AACrB,QAAM,OAAO,QAAQ;AACrB,MAAI,MAAM,QAAQ;AAElB,MAAI,CAAC,OAAO,IAAI,KAAA,MAAW,IAAI;AAC3B,YAAQ,MAAA;AAAA,MACJ,KAAK;AAAc,cAAM;AAAmB;AAAA,MAC5C,KAAK;AAAc,cAAM;AAAkB;AAAA,MAC3C,KAAK;AAAU,cAAM;AAAe;AAAA,IAAA;AAAA,EAE5C;AAGA,QAAM,YAAY,KAAK,QAAQ,mBAAmB,OAAO,EAAE,YAAA;AAC3D,QAAM,WAAW,GAAG,SAAS;AAE7B,QAAM,YAAY,KAAK,KAAK,QAAQ,IAAA,GAAO,KAAK,QAAQ;AAGxD,MAAI,CAAC,GAAG,WAAW,KAAK,QAAQ,SAAS,CAAC,GAAG;AACzC,OAAG,UAAU,KAAK,QAAQ,SAAS,GAAG,EAAE,WAAW,MAAM;AAAA,EAC7D;AAGA,MAAI,GAAG,WAAW,SAAS,GAAG;AAC1B,UAAM,YAAY,MAAM,EAAE,QAAQ;AAAA,MAC9B,SAAS,QAAQ,SAAS;AAAA,MAC1B,cAAc;AAAA,IAAA,CACjB;AAED,QAAI,EAAE,SAAS,SAAS,KAAK,CAAC,WAAW;AACrC,QAAE,OAAO,sBAAsB;AAC/B,cAAQ,KAAK,CAAC;AAAA,IAClB;AAAA,EACJ;AAEA,QAAM,IAAI,EAAE,QAAA;AACZ,IAAE,MAAM,YAAY,IAAI,KAAK;AAE7B,QAAM,WAAW,GAAG;AAEpB,QAAM,UAAU,UAAU,IAAI,EAAE,IAAI;AACpC,KAAG,cAAc,WAAW,OAAO;AAEnC,IAAE,KAAK,WAAW,IAAI,EAAE;AAExB,QAAM,YAAY,QAAQ,SAAS;AAAA;AAGnC,IAAE,KAAK,WAAW,YAAY;AAE9B,IAAE,MAAM,kEAAkE;AAC9E;AAEA,eAAe,UAAU;AACrB,QAAM,SAAS,IAAI;AACvB;AAEA,eAAe,SAAS,oBAAoB,OAAO;AAC/C,MAAI,CAAC,mBAAmB;AACpB,YAAQ,MAAA;AACR,MAAE,MAAM,yBAAyB;AAAA,EACrC,OAAO;AACH,YAAQ,MAAA;AACR,MAAE,MAAM,oCAAoC;AAAA,EAChD;AAEA,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,MAAI,YAAY,QAAQ,IAAA;AACxB,MAAI,cAAc;AAClB,MAAI,cAAc;AAClB,MAAI,eAAe;AACnB,MAAI,cAAc;AAClB,MAAI,cAAc;AAClB,MAAI,eAAe;AAGnB,QAAM,WAAW,oBAAoB,KAAK,QAAQ,SAAS,IAAI,KAAK,QAAQ,UAAU;AAEtF,MAAI,aAAa,MAAM,KAAK,SAAS,WAAW,GAAG;AAC/C,UAAM,UAAU,KAAK,WAAW,CAAC;AACjC,QAAI,CAAC,QAAQ,WAAW,IAAI,GAAG;AAC3B,kBAAY,KAAK,QAAQ,OAAO;AAAA,IACpC;AAAA,EACJ;AAGA,QAAM,cAAc,CAAC,SAAiB;AAClC,UAAM,QAAQ,KAAK,QAAQ,IAAI;AAC/B,QAAI,UAAU,MAAM,KAAK,SAAS,QAAQ,GAAG;AACzC,aAAO,KAAK,QAAQ,CAAC;AAAA,IACzB;AACA,WAAO;AAAA,EACX;AAEA,QAAM,SAAS,YAAY,OAAO;AAClC,MAAI,OAAQ,aAAY,KAAK,QAAQ,MAAM;AAE3C,QAAM,SAAS,YAAY,UAAU;AACrC,MAAI,OAAQ,eAAc;AAE1B,QAAM,aAAa,YAAY,WAAW;AAC1C,MAAI,WAAY,eAAc;AAE9B,QAAM,aAAa,YAAY,YAAY;AAC3C,MAAI,WAAY,eAAc;AAE9B,QAAM,cAAc,YAAY,YAAY;AAC5C,MAAI,YAAa,gBAAe;AAEhC,MAAI,KAAK,SAAS,gBAAgB,EAAG,eAAc;AACnD,MAAI,KAAK,SAAS,iBAAiB,EAAG,eAAc;AACpD,MAAI,KAAK,SAAS,iBAAiB,EAAG,gBAAe;AAErD,MAAI,mBAAmB;AACnB,kBAAc;AACd,mBAAe;AAAA,EACnB;AAGA,MAAI,CAAC,GAAG,WAAW,SAAS,GAAG;AAC3B,MAAE,OAAO,wBAAwB,SAAS,EAAE;AAC5C,YAAQ,KAAK,CAAC;AAAA,EAClB;AAEA,QAAM,IAAI,EAAE,QAAA;AACZ,IAAE,MAAM,wBAAwB,SAAS,EAAE;AAE3C,QAAM,WAAkB,CAAA;AAExB,MAAI;AACA,UAAM,WAAW,IAAI,gBAAgB,SAAS;AAC9C,UAAM,WAAW,MAAM,SAAS,QAAA;AAEhC,MAAE,QAAQ,8BAA8B;AAGxC,UAAM,eAAe,SAAS,gBAAgB,CAAA;AAE9C,QAAI,YAAY;AAChB,QAAI,aAAa;AAGjB,eAAW,OAAO,cAAc;AAC5B,iBAAW,SAAS,IAAI,QAAQ;AAC5B,YAAI,CAAC,SAAS,IAAI,EAAE,SAAS,MAAM,OAAO,YAAA,CAAa,GAAG;AACtD;AAAA,QACJ,OAAO;AACH;AAAA,QACJ;AAEA,YAAI,MAAM,SAAS,uBAAuB,MAAM,KAAK,SAAS,mBAAmB,GAAG;AAChF,mBAAS,KAAK;AAAA,YACV,MAAM;AAAA,YACN,SAAS;AAAA,YACT,QAAQ,WAAW,MAAM,MAAM;AAAA,YAC/B,UAAU,MAAM;AAAA,UAAA,CACnB;AAAA,QACL;AAEA,YAAI,MAAM,OAAO;AACb,qBAAW,QAAQ,MAAM,OAAO;AAC5B,gBAAI,KAAK,UAAU,oBAAoB;AACnC,uBAAS,KAAK;AAAA,gBACV,MAAM;AAAA,gBACN,SAAS;AAAA,gBACT,QAAQ,YAAY,MAAM,WAAW;AAAA,gBACrC,UAAU,EAAE,MAAM,MAAM,eAAe,MAAM,MAAM,KAAK,UAAU,UAAA;AAAA,cAAU,CAC/E;AAAA,YACL;AAAA,UACJ;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ;AAGA,QAAI,cAAmB;AACvB,QAAI,CAAC,eAAe,CAAC,aAAa;AAG9B,oBAAc,SAAS,oBAAA;AAGvB,YAAM,qBAA0C,CAAA;AAChD,UAAI,OAAO;AACX,iBAAW,OAAO,cAAc;AAC5B,YAAI,IAAI,YAAY;AAChB,qBAAW,MAAM,IAAI,YAAY;AAC7B,kBAAM,KAAK,cAAc,MAAM;AAC/B,+BAAmB,EAAE,IAAI,EAAE,GAAG,IAAI,GAAA;AAAA,UACtC;AAAA,QACJ;AAAA,MACJ;AACA,UAAI,OAAO,KAAK,kBAAkB,EAAE,SAAS,GAAG;AAC5C,oBAAY,uBAAuB,IAAI;AAAA,MAC3C;AAGA,UAAI,YAAY,OAAO;AACnB,mBAAW,WAAW,OAAO,KAAK,YAAY,KAAK,GAAG;AAClD,qBAAW,UAAU,OAAO,KAAK,YAAY,MAAM,OAAO,CAAC,GAAG;AAC1D,gBAAI,CAAC,SAAS,IAAI,EAAE,SAAS,OAAO,YAAA,CAAa,GAAG;AAChD,qBAAO,YAAY,MAAM,OAAO,EAAE,MAAM;AAAA,YAC5C;AAAA,UACJ;AACA,cAAI,OAAO,KAAK,YAAY,MAAM,OAAO,CAAC,EAAE,WAAW,GAAG;AACtD,mBAAO,YAAY,MAAM,OAAO;AAAA,UACpC;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ;AAGA,QAAI,CAAC,eAAe,aAAa;AAC7B,YAAM,gBAAgB,KAAK,MAAM,KAAK,UAAU,WAAW,CAAC;AAG5D,YAAM,kBAAkB,CAAC,QAAa;AAClC,YAAI,CAAC,OAAO,OAAO,QAAQ,SAAU;AACrC,YAAI,MAAM,QAAQ,GAAG,GAAG;AACpB,cAAI,QAAQ,eAAe;AAC3B;AAAA,QACJ;AACA,mBAAW,OAAO,OAAO,KAAK,GAAG,GAAG;AAChC,cAAI,IAAI,WAAW,IAAI,GAAG;AACtB,mBAAO,IAAI,GAAG;AAAA,UAClB,OAAO;AACH,4BAAgB,IAAI,GAAG,CAAC;AAAA,UAC5B;AAAA,QACJ;AAAA,MACJ;AACA,sBAAgB,aAAa;AAE7B,YAAM,iBAAiB,KAAK,QAAQ,WAAW;AAC/C,SAAG,cAAc,gBAAgB,KAAK,UAAU,eAAe,MAAM,CAAC,CAAC;AACvE,UAAI,CAAC,kBAAmB,GAAE,KAAK,4BAA4B,cAAc,IAAI,SAAS;AAAA,UACjF,GAAE,KAAK,4BAA4B,cAAc,IAAI,SAAS;AAAA,IACvE;AAGA,QAAI,CAAC,eAAe,aAAa;AAC7B,YAAM,iBAAiB,KAAK,QAAQ,WAAW;AAC/C,SAAG,cAAc,gBAAgB,KAAK,UAAU,aAAa,MAAM,CAAC,CAAC;AACrE,UAAI,CAAC,kBAAmB,GAAE,KAAK,6BAA6B,cAAc,IAAI,UAAU;AAAA,IAC5F;AAGA,QAAI,CAAC,cAAc;AACf,YAAM,eAAoB;AAAA,QACtB,UAAU;AAAA,QACV,MAAM,EAAE,OAAO,qBAAqB,SAAS,QAAA;AAAA,QAC7C,UAAU,CAAA;AAAA,MAAC;AAGf,iBAAW,OAAO,cAAc;AAC5B,mBAAW,SAAS,IAAI,QAAQ;AAE5B,cAAI,CAAC,SAAS,IAAI,EAAE,SAAS,MAAM,OAAO,YAAA,CAAa,GAAG;AACtD,kBAAM,YAAY,MAAM;AAExB,gBAAI,CAAC,aAAa,SAAS,SAAS,GAAG;AACnC,2BAAa,SAAS,SAAS,IAAI;AAAA,gBAC/B,SAAS;AAAA;AAAA,kBACL,aAAa,KAAK,UAAU,QAAQ,iBAAiB,GAAG,CAAC;AAAA,kBACzD,SAAS,EAAE,SAAS,EAAE,MAAM,WAAS;AAAA,kBACrC,iBAAiB,CAAC,MAAM,aAAa;AAAA,gBAAA;AAAA,cACzC;AAAA,YAER,OAAO;AAAA,YAGP;AAAA,UACJ;AAGA,cAAI,MAAM,OAAO;AACb,uBAAW,QAAQ,MAAM,OAAO;AAC5B,oBAAM,YAAY,KAAK;AACvB,kBAAI,CAAC,aAAa,SAAS,SAAS,GAAG;AACnC,6BAAa,SAAS,SAAS,IAAI;AAAA,kBAC/B,WAAW;AAAA;AAAA,oBACP,aAAa,OAAO,UAAU,QAAQ,iBAAiB,GAAG,CAAC;AAAA,oBAC3D,SAAS,EAAE,SAAS,KAAK,WAAW,EAAE,MAAM,WAAS;AAAA,kBAAE;AAAA,gBAC3D;AAAA,cAER;AAAA,YACJ;AAAA,UACJ;AAAA,QACJ;AAAA,MACJ;AAEA,YAAM,iBAAiB,KAAK,QAAQ,YAAY;AAChD,SAAG,cAAc,gBAAgB,KAAK,UAAU,cAAc,MAAM,CAAC,CAAC;AACtE,UAAI,CAAC,kBAAmB,GAAE,KAAK,6BAA6B,cAAc,IAAI,UAAU;AAAA,IAC5F;AAEA,MAAE,KAAK,qBAAqB;AAG5B,QAAI,SAAS,SAAS,GAAG;AACrB,QAAE,KAAK,GAAG,SAAS,MAAM,yCAAyC,UAAU;AAC5E,YAAM,cAAc,SAAS,OAAO,CAAC,KAAK,MAAM;AAC5C,YAAI,CAAC,IAAI,EAAE,IAAI,EAAG,KAAI,EAAE,IAAI,IAAI,CAAA;AAChC,YAAI,EAAE,IAAI,EAAE,KAAK,CAAC;AAClB,eAAO;AAAA,MACX,GAAG,CAAA,CAA2B;AAE9B,iBAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,WAAW,GAAG;AACrD,cAAM,QAAS,MAAgB;AAC/B,gBAAQ,IAAI;AAAA,IAAO,IAAI,KAAK,KAAK,IAAI;AACpC,cAAgB,MAAM,GAAG,CAAC,EAAE,QAAQ,CAAA,MAAK;AACtC,kBAAQ,IAAI,SAAS,EAAE,OAAO,IAAI,EAAE,SAAS,IAAI,EAAE,MAAM,MAAM,EAAE,EAAE;AACnE,cAAI,EAAE,SAAU,SAAQ,IAAI,YAAY,EAAE,SAAS,IAAI,IAAI,EAAE,SAAS,IAAI,EAAE;AAAA,QAChF,CAAC;AACD,YAAI,QAAQ,EAAG,SAAQ,IAAI,iBAAiB,QAAQ,CAAC,OAAO;AAAA,MAChE;AAAA,IACJ;AAEA,QAAI,CAAC,mBAAmB;AACpB,QAAE,KAAK,SAAS,SAAS,cAAc,UAAU,WAAW,SAAS;AAAA,IACzE,OAAO;AAGH,QAAE,KAAK,SAAS,SAAS,iBAAiB,SAAS;AAAA,IACvD;AAEA,MAAE,MAAM,OAAO;AAAA,EACnB,SAAS,OAAY;AACjB,MAAE,KAAK,iBAAiB;AACxB,MAAE,OAAO,UAAU,MAAM,OAAO,EAAE;AAClC,YAAQ,MAAM,KAAK;AACnB,YAAQ,KAAK,CAAC;AAAA,EAClB;AACJ;AAEA,eAAe,OAAO;AAClB,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,QAAM,UAAU,KAAK,CAAC;AAEtB,MAAI,YAAY,WAAW;AACvB,UAAM,QAAA;AAAA,EACV,WAAW,YAAY,YAAY;AAC/B,UAAM,SAAS,KAAK;AAAA,EACxB,WAAW,YAAY,cAAc,CAAC,SAAS;AAE3C,UAAM,SAAA;AAAA,EACV,OAAO;AACH,YAAQ,IAAI,cAAc;AAC1B,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,WAAW;AACvB,YAAQ,IAAI,qEAAqE;AACjF,YAAQ,IAAI,iFAAiF;AAC7F,YAAQ,IAAI,+DAA+D;AAC3E,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,QAAQ;AACpB,YAAQ,IAAI,qBAAqB;AACjC,YAAQ,IAAI,8FAA8F;AAC1G,YAAQ,IAAI,wDAAwD;AACpE,YAAQ,KAAK,CAAC;AAAA,EAClB;AACJ;AAEA,OAAO,MAAM,QAAQ,KAAK;"}
package/dist/context.d.ts CHANGED
@@ -9,6 +9,7 @@ export interface HandlerStackItem {
9
9
  name: string;
10
10
  file: string;
11
11
  line: number;
12
+ isBuiltin?: boolean;
12
13
  stateChanges?: Record<string, any>;
13
14
  startTime?: number;
14
15
  duration?: number;
@@ -125,7 +126,7 @@ export declare class ShokupanContext<State extends Record<string, any> = Record<
125
126
  private [$requestId];
126
127
  get requestId(): string;
127
128
  constructor(request: ShokupanRequest<any>, server?: Server<any>, state?: State, app?: Shokupan, signal?: AbortSignal, // Optional as it might not be provided in tests or simple creates
128
- enableMiddlewareTracking?: boolean);
129
+ enableMiddlewareTracking?: boolean, requestId?: string);
129
130
  get url(): URL;
130
131
  /**
131
132
  * Base request
@@ -180,6 +181,10 @@ export declare class ShokupanContext<State extends Record<string, any> = Record<
180
181
  * Base response object
181
182
  */
182
183
  get res(): ShokupanResponse;
184
+ /**
185
+ * Get the raw response body content (if available)
186
+ */
187
+ get responseBody(): string | ArrayBuffer | Uint8Array<ArrayBufferLike>;
183
188
  /**
184
189
  * Raw WebSocket connection
185
190
  */