apiforge-analyzer 1.1.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.
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Next.js Pages Router analyzer
3
+ * Analyzes API routes in pages/api directory
4
+ */
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ /**
8
+ * Analyze Next.js Pages Router API routes
9
+ */
10
+ export async function analyzeNextPages(pagesDir = 'pages/api') {
11
+ const routes = [];
12
+ try {
13
+ const apiDir = path.join(process.cwd(), pagesDir);
14
+ if (!fs.existsSync(apiDir)) {
15
+ console.warn('pages/api directory not found');
16
+ return routes;
17
+ }
18
+ // Recursively scan API directory
19
+ const scanDir = (dir, basePath = '') => {
20
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
21
+ for (const entry of entries) {
22
+ const fullPath = path.join(dir, entry.name);
23
+ if (entry.isDirectory()) {
24
+ // Handle dynamic routes like [id]
25
+ const dirName = entry.name.startsWith('[') && entry.name.endsWith(']')
26
+ ? `:${entry.name.slice(1, -1)}`
27
+ : entry.name;
28
+ scanDir(fullPath, `${basePath}/${dirName}`);
29
+ }
30
+ else if (entry.isFile() && /\.(ts|js)$/.test(entry.name)) {
31
+ // Convert filename to route path
32
+ const fileName = entry.name.replace(/\.(ts|js)$/, '');
33
+ let routePath = basePath;
34
+ if (fileName !== 'index') {
35
+ // Handle dynamic params like [id].ts
36
+ const paramName = fileName.startsWith('[') && fileName.endsWith(']')
37
+ ? `:${fileName.slice(1, -1)}`
38
+ : fileName;
39
+ routePath += `/${paramName}`;
40
+ }
41
+ // Read file to detect HTTP methods
42
+ const content = fs.readFileSync(fullPath, 'utf-8');
43
+ const methods = detectMethods(content);
44
+ // Create route for each detected method
45
+ methods.forEach(method => {
46
+ routes.push({
47
+ method,
48
+ path: `/api${routePath}`,
49
+ description: `${method} /api${routePath}`,
50
+ tags: ['api'],
51
+ middleware: detectMiddleware(content),
52
+ parameters: extractParameters(routePath),
53
+ responses: {
54
+ '200': { description: 'Successful response' }
55
+ }
56
+ });
57
+ });
58
+ }
59
+ }
60
+ };
61
+ scanDir(apiDir);
62
+ }
63
+ catch (error) {
64
+ console.error('Error analyzing Next.js Pages routes:', error.message);
65
+ }
66
+ return routes;
67
+ }
68
+ /**
69
+ * Detect HTTP methods in Next.js API route
70
+ */
71
+ function detectMethods(content) {
72
+ const methods = new Set();
73
+ // Check for req.method checks
74
+ const methodChecks = content.match(/req\.method\s*===?\s*['"`](\w+)['"`]/g) || [];
75
+ methodChecks.forEach(check => {
76
+ const match = check.match(/['"`](\w+)['"`]/);
77
+ if (match)
78
+ methods.add(match[1]);
79
+ });
80
+ // If no explicit methods found, assume it handles all methods
81
+ if (methods.size === 0) {
82
+ methods.add('GET');
83
+ methods.add('POST');
84
+ }
85
+ return Array.from(methods);
86
+ }
87
+ /**
88
+ * Detect middleware usage
89
+ */
90
+ function detectMiddleware(content) {
91
+ const middleware = [];
92
+ // Check for common middleware patterns
93
+ if (content.includes('auth') || content.includes('authenticate')) {
94
+ middleware.push('auth');
95
+ }
96
+ if (content.includes('cors')) {
97
+ middleware.push('cors');
98
+ }
99
+ return middleware;
100
+ }
101
+ /**
102
+ * Extract parameters from route path
103
+ */
104
+ function extractParameters(routePath) {
105
+ const params = [];
106
+ const paramMatches = routePath.match(/:(\w+)/g) || [];
107
+ paramMatches.forEach(param => {
108
+ const name = param.slice(1);
109
+ params.push({
110
+ name,
111
+ in: 'path',
112
+ required: true,
113
+ type: 'string',
114
+ description: `Path parameter: ${name}`
115
+ });
116
+ });
117
+ return params;
118
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * React Router analyzer
3
+ * Analyzes React Router route definitions
4
+ */
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ /**
8
+ * Analyze React Router routes
9
+ */
10
+ export async function analyzeReactRouter(routesFile = 'src/routes.tsx') {
11
+ const routes = [];
12
+ try {
13
+ const routesPath = path.join(process.cwd(), routesFile);
14
+ if (!fs.existsSync(routesPath)) {
15
+ console.warn(`Routes file not found: ${routesFile}`);
16
+ return routes;
17
+ }
18
+ const content = fs.readFileSync(routesPath, 'utf-8');
19
+ // Extract routes from React Router
20
+ // Look for <Route path="/..." />
21
+ const routeRegex = /<Route\s+path=["']([^"']+)["']/g;
22
+ let match;
23
+ while ((match = routeRegex.exec(content)) !== null) {
24
+ const routePath = match[1];
25
+ routes.push({
26
+ method: 'GET',
27
+ path: routePath,
28
+ description: `React Router: ${routePath}`,
29
+ tags: ['frontend'],
30
+ middleware: [],
31
+ parameters: extractParameters(routePath),
32
+ responses: {
33
+ '200': { description: 'Page rendered' }
34
+ }
35
+ });
36
+ }
37
+ // Also check for createBrowserRouter routes
38
+ const browserRouterRegex = /path:\s*["']([^"']+)["']/g;
39
+ while ((match = browserRouterRegex.exec(content)) !== null) {
40
+ const routePath = match[1];
41
+ if (!routes.find(r => r.path === routePath)) {
42
+ routes.push({
43
+ method: 'GET',
44
+ path: routePath,
45
+ description: `React Router: ${routePath}`,
46
+ tags: ['frontend'],
47
+ middleware: [],
48
+ parameters: extractParameters(routePath),
49
+ responses: {
50
+ '200': { description: 'Page rendered' }
51
+ }
52
+ });
53
+ }
54
+ }
55
+ console.log(`✅ Found ${routes.length} React Router routes`);
56
+ }
57
+ catch (error) {
58
+ console.error('Error analyzing React Router:', error.message);
59
+ }
60
+ return routes;
61
+ }
62
+ /**
63
+ * Extract parameters from route path
64
+ */
65
+ function extractParameters(routePath) {
66
+ const params = [];
67
+ const paramMatches = routePath.match(/:(\w+)/g) || [];
68
+ paramMatches.forEach(param => {
69
+ const name = param.slice(1);
70
+ params.push({
71
+ name,
72
+ in: 'path',
73
+ required: true,
74
+ type: 'string',
75
+ description: `Path parameter: ${name}`
76
+ });
77
+ });
78
+ return params;
79
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Vite analyzer
3
+ * Analyzes Vite projects with API proxy configurations
4
+ */
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ /**
8
+ * Analyze Vite API proxy routes
9
+ */
10
+ export async function analyzeVite(viteConfigPath = 'vite.config.js') {
11
+ const routes = [];
12
+ try {
13
+ const configPath = path.join(process.cwd(), viteConfigPath);
14
+ if (!fs.existsSync(configPath)) {
15
+ console.warn('vite.config.js not found');
16
+ return routes;
17
+ }
18
+ const content = fs.readFileSync(configPath, 'utf-8');
19
+ // Extract proxy configurations
20
+ const proxyRegex = /proxy:\s*{([^}]+)}/gs;
21
+ const proxyMatch = content.match(proxyRegex);
22
+ if (proxyMatch) {
23
+ const proxyContent = proxyMatch[0];
24
+ // Extract proxy routes like '/api': { target: 'http://localhost:3000' }
25
+ const routeRegex = /['"`]([^'"`]+)['"`]\s*:\s*{/g;
26
+ let match;
27
+ while ((match = routeRegex.exec(proxyContent)) !== null) {
28
+ const proxyPath = match[1];
29
+ // Create route entry
30
+ routes.push({
31
+ method: 'ALL',
32
+ path: proxyPath,
33
+ description: `Vite proxy: ${proxyPath}`,
34
+ tags: ['proxy'],
35
+ middleware: [],
36
+ responses: {
37
+ '200': { description: 'Proxied response' }
38
+ }
39
+ });
40
+ }
41
+ }
42
+ console.log(`✅ Found ${routes.length} Vite proxy routes`);
43
+ }
44
+ catch (error) {
45
+ console.error('Error analyzing Vite config:', error.message);
46
+ }
47
+ return routes;
48
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Main analyzer entry point with dual module support
3
+ */
4
+ import { analyzeExpress } from './frameworks/express.js';
5
+ import { analyzeNextPages } from './frameworks/nextjs-pages.js';
6
+ import { analyzeNextApp } from './frameworks/nextjs-app.js';
7
+ import { analyzeVite } from './frameworks/vite.js';
8
+ import { analyzeReactRouter } from './frameworks/react-router.js';
9
+ import { detectFramework } from './detect.js';
10
+ import { exportToJSON } from './export.js';
11
+ import { uploadRoutes } from './uploader.js';
12
+ // Global state for debouncing
13
+ const analysisState = new Map();
14
+ const DEBOUNCE_TIME = 5000;
15
+ /**
16
+ * Main analyze function with auto-detection
17
+ */
18
+ export async function analyze(app, options) {
19
+ // Skip in production
20
+ if (options.skipInProduction !== false && process.env.NODE_ENV === 'production') {
21
+ console.log('⏭️ Skipping analysis in production');
22
+ return [];
23
+ }
24
+ // Debounce check
25
+ if (options.runOnce !== false) {
26
+ const cached = analysisState.get(options.projectName);
27
+ const now = Date.now();
28
+ if (cached && (now - cached.timestamp) < DEBOUNCE_TIME) {
29
+ console.log(`⚡ Analysis skipped - already ran ${Math.round((now - cached.timestamp) / 1000)}s ago`);
30
+ return cached.routes;
31
+ }
32
+ }
33
+ console.log(`🔍 Analyzing ${options.projectName}...`);
34
+ // CRITICAL: Prevent restart loops - force saveJson=false when uploading
35
+ if (options.upload !== false && options.apiKey) {
36
+ options.saveJson = false;
37
+ }
38
+ let routes = [];
39
+ let framework = options.framework;
40
+ // Auto-detect framework if not specified
41
+ if (!framework) {
42
+ const detected = detectFramework(app);
43
+ framework = detected.name;
44
+ console.log(`📦 Detected framework: ${framework}`);
45
+ }
46
+ // Analyze based on framework
47
+ switch (framework) {
48
+ case 'express':
49
+ if (!app) {
50
+ console.error('❌ Express app instance required');
51
+ return [];
52
+ }
53
+ routes = await analyzeExpress(app);
54
+ break;
55
+ case 'nextjs-pages':
56
+ routes = await analyzeNextPages(options.nextDir || 'pages/api');
57
+ break;
58
+ case 'nextjs-app':
59
+ routes = await analyzeNextApp(options.nextDir || 'app');
60
+ break;
61
+ case 'vite':
62
+ routes = await analyzeVite(options.nextDir || 'vite.config.js');
63
+ break;
64
+ case 'react-router':
65
+ routes = await analyzeReactRouter(options.nextDir || 'src/routes.tsx');
66
+ break;
67
+ case 'node-http':
68
+ case 'unknown':
69
+ default:
70
+ console.warn(`⚠️ Framework "${framework}" not supported yet`);
71
+ console.log('💡 Supported: express, nextjs-pages, nextjs-app, vite, react-router');
72
+ console.log('💡 Tip: Specify framework manually: { framework: "express" }');
73
+ return [];
74
+ }
75
+ console.log(`✅ Found ${routes.length} routes`);
76
+ // Cache results
77
+ if (options.runOnce !== false) {
78
+ analysisState.set(options.projectName, {
79
+ timestamp: Date.now(),
80
+ routes
81
+ });
82
+ }
83
+ // Save JSON (only if not uploading)
84
+ if (options.saveJson) {
85
+ const filePath = options.jsonPath || 'api-export.json';
86
+ await exportToJSON(routes, options.projectName, filePath, options.baseUrl);
87
+ }
88
+ // Upload to APIForge
89
+ if (options.upload !== false && options.apiKey) {
90
+ await uploadRoutes(routes, {
91
+ projectName: options.projectName,
92
+ apiKey: options.apiKey,
93
+ baseUrl: options.baseUrl,
94
+ apiForgeUrl: options.apiForgeUrl,
95
+ });
96
+ }
97
+ return routes;
98
+ }
99
+ // Re-export framework-specific analyzers
100
+ export { analyzeExpress, analyzeNextPages, analyzeNextApp, analyzeVite, analyzeReactRouter };
101
+ export { exportToJSON } from './export.js';
102
+ export { uploadRoutes } from './uploader.js';
103
+ export { detectFramework } from './detect.js';
104
+ // Default export for CommonJS
105
+ export default {
106
+ analyze,
107
+ analyzeExpress,
108
+ analyzeNextPages,
109
+ analyzeNextApp,
110
+ analyzeVite,
111
+ analyzeReactRouter,
112
+ exportToJSON,
113
+ uploadRoutes,
114
+ detectFramework
115
+ };
@@ -0,0 +1,28 @@
1
+ import axios from 'axios';
2
+ import { enrichRoutes } from './export.js';
3
+ /**
4
+ * Upload routes to APIForge server
5
+ */
6
+ export async function uploadRoutes(routes, options) {
7
+ const url = options.apiForgeUrl || process.env.APIFORGE_URL || 'http://localhost:3000';
8
+ const enrichedRoutes = enrichRoutes(routes);
9
+ try {
10
+ const response = await axios.post(`${url}/api/upload-routes`, {
11
+ projectName: options.projectName,
12
+ routes: enrichedRoutes,
13
+ baseUrl: options.baseUrl,
14
+ framework: enrichedRoutes[0]?.framework || 'unknown',
15
+ }, {
16
+ headers: {
17
+ 'Content-Type': 'application/json',
18
+ 'X-API-Key': options.apiKey,
19
+ },
20
+ timeout: 30000,
21
+ });
22
+ console.log('✅ Uploaded to APIForge:', response.data);
23
+ }
24
+ catch (error) {
25
+ console.error('❌ Upload failed:', error.message);
26
+ throw error;
27
+ }
28
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Framework detection utilities
3
+ */
4
+ export type Framework = 'express' | 'nextjs-pages' | 'nextjs-app' | 'vite' | 'react-router' | 'node-http' | 'unknown';
5
+ export interface FrameworkInfo {
6
+ name: Framework;
7
+ version?: string;
8
+ confidence: number;
9
+ }
10
+ /**
11
+ * Detect which framework is being used
12
+ */
13
+ export declare function detectFramework(app?: any): FrameworkInfo;
@@ -0,0 +1,38 @@
1
+ export interface Route {
2
+ id?: number;
3
+ routeKey?: string;
4
+ method: string;
5
+ path: string;
6
+ tag: string;
7
+ tags?: string[];
8
+ operationId: string;
9
+ summary: string;
10
+ description?: string;
11
+ middleware: string[];
12
+ authRequired: boolean;
13
+ pathParams: string[];
14
+ queryParams: string[];
15
+ requestBodyFields: string[];
16
+ headerParams?: string[];
17
+ responseStatuses?: number[];
18
+ parameters?: Array<{
19
+ name: string;
20
+ in: 'path' | 'query' | 'header' | 'cookie';
21
+ required: boolean;
22
+ type: string;
23
+ schema?: any;
24
+ description: string;
25
+ }>;
26
+ requestBody?: any;
27
+ responses?: Record<string, any>;
28
+ linkedPaths: string[];
29
+ hasAuth: boolean;
30
+ framework: string;
31
+ sourceContext?: Record<string, any>;
32
+ }
33
+ /**
34
+ * Export routes to JSON file
35
+ */
36
+ export declare function exportToJSON(routes: Route[], projectName: string, filePath: string, baseUrl?: string): Promise<void>;
37
+ export declare function enrichRoutes(routes: Route[]): Route[];
38
+ export declare function enrichRoute(route: Route, index: number): Route;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Express analyzer — accurate route + payload extraction
3
+ */
4
+ export interface Route {
5
+ id?: number;
6
+ method: string;
7
+ path: string;
8
+ tag: string;
9
+ operationId: string;
10
+ summary: string;
11
+ middleware: string[];
12
+ authRequired: boolean;
13
+ pathParams: string[];
14
+ queryParams: string[];
15
+ requestBodyFields: string[];
16
+ linkedPaths: string[];
17
+ hasAuth: boolean;
18
+ framework: string;
19
+ responseStatuses?: number[];
20
+ headerParams?: string[];
21
+ description?: string;
22
+ }
23
+ export declare function analyzeExpress(app: any): Route[];
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Next.js App Router analyzer
3
+ * Analyzes route handlers in app directory
4
+ */
5
+ import { Route } from '../export.js';
6
+ export interface AnalyzerOptions {
7
+ projectName: string;
8
+ appDir?: string;
9
+ saveJson?: boolean;
10
+ jsonPath?: string;
11
+ upload?: boolean;
12
+ apiKey?: string;
13
+ apiForgeUrl?: string;
14
+ baseUrl?: string;
15
+ }
16
+ /**
17
+ * Analyze Next.js App Router routes
18
+ */
19
+ export declare function analyzeNextApp(options?: AnalyzerOptions | string): Promise<Route[]>;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Next.js Pages Router analyzer
3
+ * Analyzes API routes in pages/api directory
4
+ */
5
+ export interface NextPagesRoute {
6
+ method: string;
7
+ path: string;
8
+ description: string;
9
+ tags: string[];
10
+ middleware: string[];
11
+ parameters?: any[];
12
+ responses: Record<string, any>;
13
+ }
14
+ /**
15
+ * Analyze Next.js Pages Router API routes
16
+ */
17
+ export declare function analyzeNextPages(pagesDir?: string): Promise<NextPagesRoute[]>;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * React Router analyzer
3
+ * Analyzes React Router route definitions
4
+ */
5
+ export interface Route {
6
+ method: string;
7
+ path: string;
8
+ description: string;
9
+ tags: string[];
10
+ middleware: string[];
11
+ parameters?: any[];
12
+ responses: Record<string, any>;
13
+ }
14
+ /**
15
+ * Analyze React Router routes
16
+ */
17
+ export declare function analyzeReactRouter(routesFile?: string): Promise<Route[]>;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Vite analyzer
3
+ * Analyzes Vite projects with API proxy configurations
4
+ */
5
+ export interface Route {
6
+ method: string;
7
+ path: string;
8
+ description: string;
9
+ tags: string[];
10
+ middleware: string[];
11
+ parameters?: any[];
12
+ responses: Record<string, any>;
13
+ }
14
+ /**
15
+ * Analyze Vite API proxy routes
16
+ */
17
+ export declare function analyzeVite(viteConfigPath?: string): Promise<Route[]>;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Main analyzer entry point with dual module support
3
+ */
4
+ import { analyzeExpress } from './frameworks/express.js';
5
+ import { analyzeNextPages } from './frameworks/nextjs-pages.js';
6
+ import { analyzeNextApp } from './frameworks/nextjs-app.js';
7
+ import { analyzeVite } from './frameworks/vite.js';
8
+ import { analyzeReactRouter } from './frameworks/react-router.js';
9
+ import { detectFramework } from './detect.js';
10
+ import { exportToJSON } from './export.js';
11
+ import { uploadRoutes } from './uploader.js';
12
+ export interface AnalyzerOptions {
13
+ projectName: string;
14
+ baseUrl?: string;
15
+ apiKey?: string;
16
+ apiForgeUrl?: string;
17
+ upload?: boolean;
18
+ saveJson?: boolean;
19
+ jsonPath?: string;
20
+ framework?: string;
21
+ nextDir?: string;
22
+ skipInProduction?: boolean;
23
+ runOnce?: boolean;
24
+ }
25
+ /**
26
+ * Main analyze function with auto-detection
27
+ */
28
+ export declare function analyze(app: any, options: AnalyzerOptions): Promise<any[]>;
29
+ export { analyzeExpress, analyzeNextPages, analyzeNextApp, analyzeVite, analyzeReactRouter };
30
+ export { exportToJSON } from './export.js';
31
+ export { uploadRoutes } from './uploader.js';
32
+ export { detectFramework } from './detect.js';
33
+ declare const _default: {
34
+ analyze: typeof analyze;
35
+ analyzeExpress: typeof analyzeExpress;
36
+ analyzeNextPages: typeof analyzeNextPages;
37
+ analyzeNextApp: typeof analyzeNextApp;
38
+ analyzeVite: typeof analyzeVite;
39
+ analyzeReactRouter: typeof analyzeReactRouter;
40
+ exportToJSON: typeof exportToJSON;
41
+ uploadRoutes: typeof uploadRoutes;
42
+ detectFramework: typeof detectFramework;
43
+ };
44
+ export default _default;
@@ -0,0 +1,11 @@
1
+ import { Route } from './export.js';
2
+ export interface UploadOptions {
3
+ projectName: string;
4
+ baseUrl?: string;
5
+ apiForgeUrl?: string;
6
+ apiKey: string;
7
+ }
8
+ /**
9
+ * Upload routes to APIForge server
10
+ */
11
+ export declare function uploadRoutes(routes: Route[], options: UploadOptions): Promise<void>;
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "apiforge-analyzer",
3
+ "version": "1.1.0",
4
+ "description": "Universal API documentation generator for Express, Next.js, Vite, React Router, and Node.js",
5
+ "type": "module",
6
+ "main": "./dist/cjs/index.js",
7
+ "module": "./dist/esm/index.js",
8
+ "types": "./dist/types/index.d.ts",
9
+ "bin": {
10
+ "apiforge-analyzer": "./dist/esm/cli.js",
11
+ "apiforge": "./dist/esm/cli.js"
12
+ },
13
+ "exports": {
14
+ ".": {
15
+ "import": "./dist/esm/index.js",
16
+ "require": "./dist/cjs/index.js",
17
+ "types": "./dist/types/index.d.ts"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "README.md"
23
+ ],
24
+ "scripts": {
25
+ "build": "npm run build:esm && npm run build:cjs && npm run build:types",
26
+ "build:esm": "tsc -p tsconfig.esm.json",
27
+ "build:cjs": "tsc -p tsconfig.cjs.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json",
28
+ "build:types": "tsc -p tsconfig.types.json",
29
+ "prepublishOnly": "npm run build"
30
+ },
31
+ "keywords": [
32
+ "api",
33
+ "documentation",
34
+ "openapi",
35
+ "swagger",
36
+ "express",
37
+ "nextjs",
38
+ "vite",
39
+ "react-router",
40
+ "nodejs",
41
+ "analyzer",
42
+ "cli"
43
+ ],
44
+ "author": "APIForge",
45
+ "license": "MIT",
46
+ "peerDependencies": {
47
+ "express": "^4.0.0 || ^5.0.0"
48
+ },
49
+ "peerDependenciesMeta": {
50
+ "express": {
51
+ "optional": true
52
+ }
53
+ },
54
+ "dependencies": {
55
+ "@clack/prompts": "^0.9.1",
56
+ "axios": "^1.6.0"
57
+ },
58
+ "devDependencies": {
59
+ "@types/express": "^4.17.21",
60
+ "@types/node": "^20.0.0",
61
+ "typescript": "^5.3.0"
62
+ }
63
+ }