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.
- package/dist/{analyzer-CKLGLFtx.cjs → analyzer-BAhvpNY_.cjs} +2 -7
- package/dist/{analyzer-CKLGLFtx.cjs.map → analyzer-BAhvpNY_.cjs.map} +1 -1
- package/dist/{analyzer-BqIe1p0R.js → analyzer-CnKnQ5KV.js} +3 -8
- package/dist/{analyzer-BqIe1p0R.js.map → analyzer-CnKnQ5KV.js.map} +1 -1
- package/dist/{analyzer.impl-D9Yi1Hax.cjs → analyzer.impl-CfpMu4-g.cjs} +586 -40
- package/dist/analyzer.impl-CfpMu4-g.cjs.map +1 -0
- package/dist/{analyzer.impl-CV6W1Eq7.js → analyzer.impl-DCiqlXI5.js} +586 -40
- package/dist/analyzer.impl-DCiqlXI5.js.map +1 -0
- package/dist/cli.cjs +206 -18
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +206 -18
- package/dist/cli.js.map +1 -1
- package/dist/context.d.ts +6 -1
- package/dist/index.cjs +2405 -1008
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2402 -1006
- package/dist/index.js.map +1 -1
- package/dist/plugins/application/api-explorer/static/explorer-client.mjs +423 -30
- package/dist/plugins/application/api-explorer/static/style.css +351 -10
- package/dist/plugins/application/api-explorer/static/theme.css +7 -2
- package/dist/plugins/application/asyncapi/generator.d.ts +4 -0
- package/dist/plugins/application/asyncapi/static/asyncapi-client.mjs +154 -22
- package/dist/plugins/application/asyncapi/static/style.css +24 -8
- package/dist/plugins/application/dashboard/fetch-interceptor.d.ts +107 -0
- package/dist/plugins/application/dashboard/metrics-collector.d.ts +38 -2
- package/dist/plugins/application/dashboard/plugin.d.ts +44 -1
- package/dist/plugins/application/dashboard/static/charts.js +127 -62
- package/dist/plugins/application/dashboard/static/client.js +160 -0
- package/dist/plugins/application/dashboard/static/graph.mjs +167 -56
- package/dist/plugins/application/dashboard/static/reactflow.css +20 -10
- package/dist/plugins/application/dashboard/static/registry.js +112 -8
- package/dist/plugins/application/dashboard/static/requests.js +868 -58
- package/dist/plugins/application/dashboard/static/styles.css +186 -14
- package/dist/plugins/application/dashboard/static/tabs.js +44 -9
- package/dist/plugins/application/dashboard/static/theme.css +7 -2
- package/dist/plugins/application/openapi/analyzer.impl.d.ts +61 -1
- package/dist/plugins/application/openapi/openapi.d.ts +3 -0
- package/dist/plugins/application/shared/ast-utils.d.ts +7 -0
- package/dist/router.d.ts +55 -16
- package/dist/shokupan.d.ts +7 -2
- package/dist/util/adapter/adapters.d.ts +19 -0
- package/dist/util/adapter/filesystem.d.ts +20 -0
- package/dist/util/controller-scanner.d.ts +4 -0
- package/dist/util/cpu-monitor.d.ts +2 -0
- package/dist/util/middleware-tracker.d.ts +10 -0
- package/dist/util/types.d.ts +37 -0
- package/package.json +5 -5
- package/dist/analyzer.impl-CV6W1Eq7.js.map +0 -1
- package/dist/analyzer.impl-D9Yi1Hax.cjs.map +0 -1
- package/dist/http-server-BEMPIs33.cjs +0 -85
- package/dist/http-server-BEMPIs33.cjs.map +0 -1
- package/dist/http-server-CCeagTyU.js +0 -68
- package/dist/http-server-CCeagTyU.js.map +0 -1
- 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":";;;;;;;;;;;;;;;;;;;;;;;;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;AACRA,eAAE,MAAM,yBAAyB;AAGjC,MAAI,CAAC,GAAG,WAAW,cAAc,GAAG;AAChCA,iBAAE,KAAK,mFAAmF;AAAA,EAC9F;AAEA,QAAM,UAAU,MAAMA,aAAE;AAAA,IACpB;AAAA,MACI,MAAM,MAAMA,aAAE,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,MAAMA,aAAE,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,MAAMA,aAAE,KAAK;AAAA,QACd,SAAS;AAAA,QACT,aAAa;AAAA,MAAA,CAChB;AAAA,IAAA;AAAA,IAEL;AAAA,MACI,UAAU,MAAM;AACZA,qBAAE,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,MAAMA,aAAE,QAAQ;AAAA,MAC9B,SAAS,QAAQ,SAAS;AAAA,MAC1B,cAAc;AAAA,IAAA,CACjB;AAED,QAAIA,aAAE,SAAS,SAAS,KAAK,CAAC,WAAW;AACrCA,mBAAE,OAAO,sBAAsB;AAC/B,cAAQ,KAAK,CAAC;AAAA,IAClB;AAAA,EACJ;AAEA,QAAM,IAAIA,aAAE,QAAA;AACZ,IAAE,MAAM,YAAY,IAAI,KAAK;AAE7B,QAAMC,SAAAA,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;AAGnCD,eAAE,KAAK,WAAW,YAAY;AAE9BA,eAAE,MAAM,kEAAkE;AAC9E;AAEA,eAAe,UAAU;AACrB,QAAM,SAAS,IAAI;AACvB;AAEA,eAAe,SAAS,oBAAoB,OAAO;AAC/C,MAAI,CAAC,mBAAmB;AACpB,YAAQ,MAAA;AACRA,iBAAE,MAAM,yBAAyB;AAAA,EACrC,OAAO;AACH,YAAQ,MAAA;AACRA,iBAAE,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;AAC3BA,iBAAE,OAAO,wBAAwB,SAAS,EAAE;AAC5C,YAAQ,KAAK,CAAC;AAAA,EAClB;AAEA,QAAM,IAAIA,aAAE,QAAA;AACZ,IAAE,MAAM,wBAAwB,SAAS,EAAE;AAE3C,QAAM,WAAkB,CAAA;AAExB,MAAI;AACA,UAAME,aAAW,IAAIC,SAAAA,gBAAgB,SAAS;AAC9C,UAAM,WAAW,MAAMD,WAAS,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,oBAAcA,WAAS,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,kBAAmBF,cAAE,KAAK,4BAA4B,cAAc,IAAI,SAAS;AAAA,UACjFA,cAAE,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,kBAAmBA,cAAE,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,kBAAmBA,cAAE,KAAK,6BAA6B,cAAc,IAAI,UAAU;AAAA,IAC5F;AAEA,MAAE,KAAK,qBAAqB;AAG5B,QAAI,SAAS,SAAS,GAAG;AACrBA,mBAAE,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;AACpBA,mBAAE,KAAK,SAAS,SAAS,cAAc,UAAU,WAAW,SAAS;AAAA,IACzE,OAAO;AAGHA,mBAAE,KAAK,SAAS,SAAS,iBAAiB,SAAS;AAAA,IACvD;AAEAA,iBAAE,MAAM,OAAO;AAAA,EACnB,SAAS,OAAY;AACjB,MAAE,KAAK,iBAAiB;AACxBA,iBAAE,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/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 {
|
|
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
|
-
|
|
138
|
-
|
|
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
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
|
150
|
-
|
|
151
|
-
|
|
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
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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("
|
|
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":"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"}
|
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
|
*/
|