Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ A CLI tool to generate TypeScript type definitions from OpenAPI/Swagger specific
- βœ… Generates types for schemas, request bodies, parameters, and responses
- βœ… Includes JSDoc comments from OpenAPI descriptions
- βœ… Configurable endpoint naming via path prefix skipping
- βœ… Generates API endpoint URL constants for type-safe API calls
- βœ… Configurable via config file or CLI flags
- βœ… Optional Prettier formatting for generated code

Expand Down Expand Up @@ -79,6 +80,7 @@ npx @kolot/swagger-type-parser --config configs/swagger.config.json
| `--pretty` | | Format generated code with Prettier | `false` |
| `--verbose` | | Log verbose debug information | `false` |
| `--path-prefix-skip` | | Number of path segment pairs to skip when generating endpoint names (e.g., 1 = skip first 2 segments: "/api/v1/auth/login" β†’ "auth_login") | `0` |
| `--generate-api-endpoints` | | Generate API endpoint URL constants for easy access from frontend | `false` |

**Note:** CLI flags override values from the config file.

Expand Down Expand Up @@ -118,6 +120,8 @@ src/api/types/
β”‚ β”‚ β”œβ”€β”€ users_list.ts
β”‚ β”‚ └── users_create.ts
β”‚ └── health.ts # Endpoints with single segment (e.g., /api/v1/health)
β”œβ”€β”€ api/ # API endpoint URL constants (if --generate-api-endpoints is enabled)
β”‚ └── index.ts # apiEndpoints object with nested structure
└── common/ # Common utility types
└── Http.ts # HttpMethod, RequestConfig, etc.
```
Expand Down Expand Up @@ -177,6 +181,101 @@ const result = await login('user@example.com', 'password123');
// result is typed as auth_login_200Response
```

### Using API Endpoint Constants

When `--generate-api-endpoints` flag is enabled, the tool generates a nested object structure with all API endpoint URLs, organized by their folder hierarchy. This eliminates the need to manually write URL strings:

```typescript
import { apiEndpoints } from './api/types/api';
import type { auth_login_200Response, auth_login_RequestBody } from './api/types/endpoints/auth/auth_login';

async function login(email: string, password: string): Promise<auth_login_200Response> {
const body: auth_login_RequestBody = { email, password };

const response = await fetch(apiEndpoints.auth.auth_login, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});

if (!response.ok) {
throw new Error('Login failed');
}

return response.json();
}

// Or with an API client
await apiClient.post<auth_login_200Response>(
apiEndpoints.auth.auth_login,
payload,
{
skipAuth: true, // Public endpoint
},
);
```

### Handling Path Parameters

For endpoints with path parameters (e.g., `/api/v1/users/profile/{user_id}`), use the `buildUrl` utility function to replace parameters:

```typescript
import { apiEndpoints, buildUrl } from './api/types/api';
import type { users_profile_200Response, users_profile_PathParams } from './api/types/endpoints/users/users_profile';

// Single parameter
const url = buildUrl(apiEndpoints.users.users_profile, { user_id: '123' });
// Returns: '/api/v1/users/profile/123'

// Multiple parameters
const url2 = buildUrl(
apiEndpoints.dynamic_fields.dynamic_fields_entities,
{ entity_type: 'user_profile', entity_id: '456' }
);
// Returns: '/api/v1/dynamic-fields/entities/user_profile/456'

// Type-safe usage with PathParams
async function getUserProfile(userId: string): Promise<users_profile_200Response> {
const params: users_profile_PathParams = { user_id: userId };
const url = buildUrl(apiEndpoints.users.users_profile, params);

const response = await fetch(url, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
});

return response.json();
}

// Endpoints without parameters work as-is
const loginUrl = apiEndpoints.auth.auth_login;
// No need to use buildUrl for endpoints without parameters
```

The `apiEndpoints` object structure matches the endpoint folder organization:

```typescript
// Generated structure example (with pathPrefixSkip: 1)
export const apiEndpoints = {
auth: {
auth_login: '/api/v1/auth/login',
auth_register: '/api/v1/auth/register',
auth_me: '/api/v1/auth/me',
},
users: {
users_list: '/api/v1/users',
users_detail: '/api/v1/users/{id}',
},
// ...
} as const;
```

**Benefits:**
- βœ… Type-safe endpoint URLs (autocomplete support)
- βœ… No manual URL string writing
- βœ… Automatic updates when API changes
- βœ… Consistent with endpoint type organization

### Generated Type Naming Convention

- **Schemas**: Use the schema name from `components.schemas` (e.g., `User`, `Order`)
Expand Down Expand Up @@ -225,6 +324,7 @@ The config file (`swagger-type-parser.config.json`) supports the following optio
- **`pretty`** (optional): Format generated code with Prettier (requires Prettier to be installed)
- **`verbose`** (optional): Enable verbose logging
- **`pathPrefixSkip`** (optional): Number of path segment pairs to skip when generating endpoint names
- **`generateApiEndpoints`** (optional): Generate API endpoint URL constants for easy access from frontend
- `0` (default): Use full path - `/api/v1/auth/login` β†’ `api_v1_auth_login`
- `1`: Skip first 2 segments - `/api/v1/auth/login` β†’ `auth_login`
- `2`: Skip first 4 segments - `/api/v1/auth/login` β†’ (empty, would be `root`)
Expand Down
3 changes: 3 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ async function main(): Promise<void> {
.option('--pretty', 'Format generated code with Prettier', false)
.option('--verbose', 'Log verbose debug information', false)
.option('--path-prefix-skip <number>', 'Number of path segments to skip from beginning (e.g., 1 = skip first 2 segments: "/api/v1/auth/login" -> "auth_login")', (value) => parseInt(value, 10))
.option('--generate-api-endpoints', 'Generate API endpoint URL constants for easy access from frontend', false)
.parse(process.argv);

const options = program.opts<Config & { config?: string }>();
Expand All @@ -45,6 +46,7 @@ async function main(): Promise<void> {
pretty: options.pretty,
verbose: options.verbose,
pathPrefixSkip: options.pathPrefixSkip,
generateApiEndpoints: options.generateApiEndpoints,
},
options.config
);
Expand Down Expand Up @@ -91,6 +93,7 @@ async function main(): Promise<void> {
pretty: config.pretty,
verbose: config.verbose,
pathPrefixSkip: config.pathPrefixSkip,
generateApiEndpoints: config.generateApiEndpoints,
});

console.log(`βœ… Successfully generated TypeScript types in ${config.output}`);
Expand Down
56 changes: 52 additions & 4 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,54 @@ export function loadConfigFile(configPath: string): Config | null {
}
}

/**
* Checks if a flag was explicitly provided in CLI arguments
* @param flagName - Flag name (e.g., '--generate-api-endpoints')
* @returns True if flag was explicitly provided
*/
function isFlagProvided(flagName: string): boolean {
return process.argv.includes(flagName);
}

/**
* Filters out undefined values from a configuration object
* For boolean flags, only includes them if they were explicitly provided in CLI
* @param config - Configuration object to filter
* @returns Configuration object without undefined values and non-explicit boolean flags
*/
function filterUndefined(config: Config): Partial<Config> {
const filtered: Partial<Config> = {};

if (config.input !== undefined) {
filtered.input = config.input;
}
if (config.output !== undefined) {
filtered.output = config.output;
}
if (config.clean !== undefined) {
filtered.clean = config.clean;
}
if (config.pretty !== undefined) {
filtered.pretty = config.pretty;
}
if (config.verbose !== undefined) {
filtered.verbose = config.verbose;
}
if (config.pathPrefixSkip !== undefined) {
filtered.pathPrefixSkip = config.pathPrefixSkip;
}
// For boolean flags, only include if explicitly provided in CLI
if (config.generateApiEndpoints !== undefined && isFlagProvided('--generate-api-endpoints')) {
filtered.generateApiEndpoints = config.generateApiEndpoints;
}

return filtered;
}

/**
* Merges CLI arguments with config file values (CLI takes precedence)
* @param cliConfig - Configuration from CLI arguments
* Only non-undefined CLI values override file config values
* @param cliConfig - Configuration from CLI arguments (may contain undefined values)
* @param configPath - Optional path to config file
* @returns Merged configuration
*/
Expand All @@ -42,10 +87,13 @@ export function mergeConfig(cliConfig: Config, configPath?: string): Config {
fileConfig = loadConfigFile(defaultPath);
}

// CLI config overrides file config
// Filter out undefined values from CLI config to avoid overwriting file config
const filteredCliConfig = filterUndefined(cliConfig);

// CLI config overrides file config (only non-undefined values)
return {
...fileConfig,
...cliConfig,
...(fileConfig || {}),
...filteredCliConfig,
};
}

Expand Down
Loading