New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

@cushin/api-codegen

Package Overview
Dependencies
Maintainers
1
Versions
42
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@cushin/api-codegen - npm Package Compare versions

Comparing version
5.0.8
to
6.0.0
+630
-2
dist/cli.js

@@ -31,2 +31,3 @@ #!/usr/bin/env node

const outputDir = path.resolve(rootDir, userConfig.output);
const swaggerSourcePath = userConfig.swaggerSource ? path.resolve(rootDir, userConfig.swaggerSource) : void 0;
const generateHooks = userConfig.generateHooks ?? true;

@@ -42,2 +43,3 @@ const generateServerActions = userConfig.generateServerActions ?? userConfig.provider === "nextjs";

outputDir,
swaggerSourcePath,
generateHooks,

@@ -1016,2 +1018,564 @@ generateServerActions,

import { fileURLToPath } from "url";
import fs11 from "fs/promises";
import path12 from "path";
// src/swagger/parser.ts
import fs10 from "fs";
import path11 from "path";
async function parseOpenAPISpec(filePath) {
const content = await fs10.promises.readFile(filePath, "utf-8");
const ext = path11.extname(filePath).toLowerCase();
let spec;
if (ext === ".json") {
spec = JSON.parse(content);
} else if (ext === ".yaml" || ext === ".yml") {
const yaml = await import("yaml");
spec = yaml.parse(content);
} else {
throw new Error(
`Unsupported file format: ${ext}. Only .json, .yaml, and .yml are supported.`
);
}
if (!spec.openapi || !spec.openapi.startsWith("3.")) {
throw new Error(
`Unsupported OpenAPI version: ${spec.openapi}. Only OpenAPI 3.0 and 3.1 are supported.`
);
}
return spec;
}
function extractEndpoints(spec) {
const endpoints = [];
for (const [path14, pathItem] of Object.entries(spec.paths)) {
const methods = [
"get",
"post",
"put",
"delete",
"patch",
"head",
"options"
];
for (const method of methods) {
const operation = pathItem[method];
if (!operation) continue;
const endpoint = parseOperation(
path14,
method,
operation,
pathItem,
spec
);
endpoints.push(endpoint);
}
}
return endpoints;
}
function parseOperation(path14, method, operation, pathItem, spec) {
const allParameters = [
...pathItem.parameters || [],
...operation.parameters || []
];
const pathParams = [];
const queryParams = [];
for (const param of allParameters) {
const resolved = resolveParameter(param, spec);
const parsed = {
name: resolved.name,
type: schemaToTypeString(resolved.schema),
required: resolved.required ?? resolved.in === "path",
description: resolved.description,
schema: resolved.schema
// Keep original schema
};
if (resolved.in === "path") {
pathParams.push(parsed);
} else if (resolved.in === "query") {
queryParams.push(parsed);
}
}
let requestBody;
if (operation.requestBody) {
const resolved = resolveRequestBody(operation.requestBody, spec);
const content = resolved.content?.["application/json"];
if (content) {
requestBody = {
type: schemaToTypeString(content.schema),
required: resolved.required ?? false,
description: resolved.description,
schema: content.schema
};
}
}
let response;
const responses = operation.responses;
const successStatus = responses["200"] || responses["201"] || responses["204"];
if (successStatus) {
const statusCode = responses["200"] ? "200" : responses["201"] ? "201" : "204";
const resolved = resolveResponse(successStatus, spec);
const content = resolved.content?.["application/json"];
if (content || statusCode === "204") {
response = {
statusCode,
type: content ? schemaToTypeString(content.schema) : "void",
description: resolved.description,
schema: content?.schema || { type: "null" }
};
}
}
if (!response) {
for (const [statusCode, res] of Object.entries(responses)) {
if (statusCode.startsWith("2")) {
const resolved = resolveResponse(res, spec);
const content = resolved.content?.["application/json"];
response = {
statusCode,
type: content ? schemaToTypeString(content.schema) : "void",
description: resolved.description,
schema: content?.schema || { type: "null" }
};
break;
}
}
}
return {
path: path14,
method: method.toUpperCase(),
operationId: operation.operationId,
summary: operation.summary,
description: operation.description,
tags: operation.tags,
pathParams: pathParams.length > 0 ? pathParams : void 0,
queryParams: queryParams.length > 0 ? queryParams : void 0,
requestBody,
response
};
}
function resolveParameter(param, spec) {
if ("$ref" in param && param.$ref) {
const refPath = param.$ref.replace("#/components/parameters/", "");
const resolved = spec.components?.parameters?.[refPath];
if (!resolved) {
throw new Error(`Cannot resolve parameter reference: ${param.$ref}`);
}
return resolved;
}
return param;
}
function resolveRequestBody(requestBody, spec) {
if ("$ref" in requestBody && requestBody.$ref) {
const refPath = requestBody.$ref.replace("#/components/requestBodies/", "");
const resolved = spec.components?.requestBodies?.[refPath];
if (!resolved) {
throw new Error(`Cannot resolve request body reference: ${requestBody.$ref}`);
}
return resolved;
}
return requestBody;
}
function resolveResponse(response, spec) {
if ("$ref" in response && response.$ref) {
const refPath = response.$ref.replace("#/components/responses/", "");
const resolved = spec.components?.responses?.[refPath];
if (!resolved) {
throw new Error(`Cannot resolve response reference: ${response.$ref}`);
}
return resolved;
}
return response;
}
function schemaToTypeString(schema) {
if (!schema) return "any";
if (schema.$ref) {
const parts = schema.$ref.split("/");
return parts[parts.length - 1];
}
if (schema.type) {
switch (schema.type) {
case "string":
return "string";
case "number":
case "integer":
return "number";
case "boolean":
return "boolean";
case "array":
if (schema.items) {
return `${schemaToTypeString(schema.items)}[]`;
}
return "any[]";
case "object":
return "object";
default:
return "any";
}
}
if (schema.allOf) {
return schema.allOf.map(schemaToTypeString).join(" & ");
}
if (schema.oneOf || schema.anyOf) {
const schemas = schema.oneOf || schema.anyOf;
return schemas.map(schemaToTypeString).join(" | ");
}
return "any";
}
// src/swagger/config-generator.ts
function groupEndpointsByTags(endpoints) {
const grouped = /* @__PURE__ */ new Map();
for (const endpoint of endpoints) {
const tag = endpoint.tags && endpoint.tags.length > 0 ? endpoint.tags[0] : "default";
if (!grouped.has(tag)) {
grouped.set(tag, []);
}
grouped.get(tag).push(endpoint);
}
return grouped;
}
function generateConfigFile(endpoints, spec, baseUrl) {
const lines = [];
lines.push('import { defineConfig, defineEndpoint } from "@cushin/api-runtime";');
lines.push('import { z } from "zod";');
lines.push("");
if (spec.components?.schemas) {
lines.push("// Schema definitions from OpenAPI components");
for (const [name, schema] of Object.entries(spec.components.schemas)) {
const zodSchema = convertSchemaToZod(schema, spec);
lines.push(`const ${name}Schema = ${zodSchema};`);
}
lines.push("");
}
lines.push("// Endpoint definitions");
const endpointNames = [];
for (const endpoint of endpoints) {
const name = generateEndpointName(endpoint);
endpointNames.push(name);
const path14 = convertPathFormat(endpoint.path);
lines.push(`const ${name} = defineEndpoint({`);
lines.push(` path: "${path14}",`);
lines.push(` method: "${endpoint.method}",`);
if (endpoint.pathParams && endpoint.pathParams.length > 0) {
const paramsSchema = generateParamsSchema(endpoint.pathParams, spec);
lines.push(` params: ${paramsSchema},`);
}
if (endpoint.queryParams && endpoint.queryParams.length > 0) {
const querySchema = generateQuerySchema(endpoint.queryParams, spec);
lines.push(` query: ${querySchema},`);
}
if (endpoint.requestBody) {
const bodySchema = convertSchemaToZod(endpoint.requestBody.schema, spec);
lines.push(` body: ${bodySchema},`);
}
if (endpoint.response) {
const responseSchema = convertSchemaToZod(endpoint.response.schema, spec);
lines.push(` response: ${responseSchema},`);
} else {
lines.push(` response: z.any(),`);
}
if (endpoint.tags && endpoint.tags.length > 0) {
const tagsStr = endpoint.tags.map((t) => `"${t}"`).join(", ");
lines.push(` tags: [${tagsStr}],`);
}
if (endpoint.description || endpoint.summary) {
const desc = endpoint.description || endpoint.summary;
lines.push(` description: "${escapeString(desc)}",`);
}
lines.push("});");
lines.push("");
}
lines.push("// API Configuration");
lines.push("export const apiConfig = defineConfig({");
if (baseUrl) {
lines.push(` baseUrl: "${baseUrl}",`);
} else if (spec.servers && spec.servers.length > 0) {
lines.push(` baseUrl: "${spec.servers[0].url}",`);
}
lines.push(" endpoints: {");
for (const name of endpointNames) {
lines.push(` ${name},`);
}
lines.push(" },");
lines.push("});");
lines.push("");
return lines.join("\n");
}
function generateEndpointName(endpoint) {
if (endpoint.operationId) {
return toCamelCase(endpoint.operationId);
}
const method = endpoint.method.toLowerCase();
const pathParts = endpoint.path.split("/").filter((p) => p && !p.startsWith(":")).map((p) => p.replace(/[^a-zA-Z0-9]/g, ""));
const parts = [method, ...pathParts];
return toCamelCase(parts.join("_"));
}
function toCamelCase(str) {
return str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toLowerCase());
}
function generateParamsSchema(params, spec) {
const props = [];
for (const param of params) {
const zodType = convertSchemaToZod(param.schema, spec);
props.push(` ${param.name}: ${zodType}`);
}
return `z.object({
${props.join(",\n")}
})`;
}
function generateQuerySchema(params, spec) {
const props = [];
for (const param of params) {
let zodType = convertSchemaToZod(param.schema, spec);
if (!param.required) {
zodType += ".optional()";
}
props.push(` ${param.name}: ${zodType}`);
}
return `z.object({
${props.join(",\n")}
})`;
}
function convertSchemaToZod(schema, spec) {
if (!schema) return "z.any()";
if (schema.$ref) {
const refName = schema.$ref.split("/").pop();
if (refName && spec.components?.schemas?.[refName]) {
return `${refName}Schema`;
}
return "z.any()";
}
if (schema.type) {
switch (schema.type) {
case "string":
if (schema.enum) {
const values = schema.enum.map((v) => `"${v}"`).join(", ");
return `z.enum([${values}])`;
}
return "z.string()";
case "number":
case "integer":
return "z.number()";
case "boolean":
return "z.boolean()";
case "array":
if (schema.items) {
const itemSchema = convertSchemaToZod(schema.items, spec);
return `z.array(${itemSchema})`;
}
return "z.array(z.any())";
case "object":
if (schema.properties) {
const props = [];
const required = schema.required || [];
for (const [propName, propSchema] of Object.entries(
schema.properties
)) {
let zodType = convertSchemaToZod(propSchema, spec);
if (!required.includes(propName)) {
zodType += ".optional()";
}
props.push(` ${propName}: ${zodType}`);
}
return `z.object({
${props.join(",\n")}
})`;
}
return "z.object({})";
case "null":
return "z.null()";
default:
return "z.any()";
}
}
if (schema.allOf) {
const schemas = schema.allOf.map((s) => convertSchemaToZod(s, spec));
return schemas.join(".and(");
}
if (schema.oneOf || schema.anyOf) {
const schemas = (schema.oneOf || schema.anyOf).map(
(s) => convertSchemaToZod(s, spec)
);
return `z.union([${schemas.join(", ")}])`;
}
return "z.any()";
}
function escapeString(str) {
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
}
function convertPathFormat(path14) {
return path14.replace(/\{([^}]+)\}/g, ":$1");
}
function generateModuleFile(tag, endpoints, spec) {
const lines = [];
lines.push('import { defineEndpoint } from "@cushin/api-runtime";');
lines.push('import { z } from "zod";');
lines.push("");
const usedSchemas = /* @__PURE__ */ new Set();
for (const endpoint of endpoints) {
collectUsedSchemas(endpoint, usedSchemas, spec);
}
if (usedSchemas.size > 0 && spec.components?.schemas) {
lines.push("// Schema definitions");
const sortedSchemas = sortSchemasByDependencies(Array.from(usedSchemas), spec);
for (const schemaName of sortedSchemas) {
const schema = spec.components.schemas[schemaName];
if (schema) {
const zodSchema = convertSchemaToZod(schema, spec);
lines.push(`const ${schemaName}Schema = ${zodSchema};`);
}
}
lines.push("");
}
lines.push("// Endpoint definitions");
const endpointNames = [];
for (const endpoint of endpoints) {
const name = generateEndpointName(endpoint);
endpointNames.push(name);
const path14 = convertPathFormat(endpoint.path);
lines.push(`export const ${name} = defineEndpoint({`);
lines.push(` path: "${path14}",`);
lines.push(` method: "${endpoint.method}",`);
if (endpoint.pathParams && endpoint.pathParams.length > 0) {
const paramsSchema = generateParamsSchema(endpoint.pathParams, spec);
lines.push(` params: ${paramsSchema},`);
}
if (endpoint.queryParams && endpoint.queryParams.length > 0) {
const querySchema = generateQuerySchema(endpoint.queryParams, spec);
lines.push(` query: ${querySchema},`);
}
if (endpoint.requestBody) {
const bodySchema = convertSchemaToZod(endpoint.requestBody.schema, spec);
lines.push(` body: ${bodySchema},`);
}
if (endpoint.response) {
const responseSchema = convertSchemaToZod(endpoint.response.schema, spec);
lines.push(` response: ${responseSchema},`);
} else {
lines.push(` response: z.any(),`);
}
if (endpoint.tags && endpoint.tags.length > 0) {
const tagsStr = endpoint.tags.map((t) => `"${t}"`).join(", ");
lines.push(` tags: [${tagsStr}],`);
}
if (endpoint.description || endpoint.summary) {
const desc = endpoint.description || endpoint.summary;
lines.push(` description: "${escapeString(desc)}",`);
}
lines.push("});");
lines.push("");
}
return lines.join("\n");
}
function sortSchemasByDependencies(schemaNames, spec) {
const sorted = [];
const visited = /* @__PURE__ */ new Set();
const visiting = /* @__PURE__ */ new Set();
function visit(name) {
if (visited.has(name)) return;
if (visiting.has(name)) {
return;
}
visiting.add(name);
const schema = spec.components?.schemas?.[name];
if (schema) {
const deps = /* @__PURE__ */ new Set();
extractSchemaNames(schema, deps, spec);
for (const dep of deps) {
if (dep !== name && schemaNames.includes(dep)) {
visit(dep);
}
}
}
visiting.delete(name);
if (!visited.has(name)) {
visited.add(name);
sorted.push(name);
}
}
for (const name of schemaNames) {
visit(name);
}
return sorted;
}
function collectUsedSchemas(endpoint, usedSchemas, spec) {
if (endpoint.requestBody?.schema) {
extractSchemaNames(endpoint.requestBody.schema, usedSchemas, spec);
}
if (endpoint.response?.schema) {
extractSchemaNames(endpoint.response.schema, usedSchemas, spec);
}
if (endpoint.pathParams) {
for (const param of endpoint.pathParams) {
extractSchemaNames(param.schema, usedSchemas, spec);
}
}
if (endpoint.queryParams) {
for (const param of endpoint.queryParams) {
extractSchemaNames(param.schema, usedSchemas, spec);
}
}
}
function extractSchemaNames(schema, names, spec) {
if (!schema) return;
if (schema.$ref) {
const schemaName = schema.$ref.split("/").pop();
if (schemaName) {
names.add(schemaName);
const referencedSchema = spec.components?.schemas?.[schemaName];
if (referencedSchema) {
extractSchemaNames(referencedSchema, names, spec);
}
}
return;
}
if (schema.properties) {
for (const prop of Object.values(schema.properties)) {
extractSchemaNames(prop, names, spec);
}
}
if (schema.items) {
extractSchemaNames(schema.items, names, spec);
}
if (schema.allOf) {
for (const s of schema.allOf) {
extractSchemaNames(s, names, spec);
}
}
if (schema.oneOf) {
for (const s of schema.oneOf) {
extractSchemaNames(s, names, spec);
}
}
if (schema.anyOf) {
for (const s of schema.anyOf) {
extractSchemaNames(s, names, spec);
}
}
}
function generateIndexFile(tagModules, spec, baseUrl) {
const lines = [];
lines.push('import { defineConfig } from "@cushin/api-runtime";');
lines.push("");
for (const [tag] of tagModules) {
const moduleFileName = tag.toLowerCase().replace(/[^a-z0-9]/g, "-");
lines.push(`import * as ${toCamelCase(tag)}Module from "./${moduleFileName}.js";`);
}
lines.push("");
lines.push("export const apiConfig = defineConfig({");
const url = baseUrl || (spec.servers && spec.servers.length > 0 ? spec.servers[0].url : void 0);
if (url) {
lines.push(` baseUrl: "${url}",`);
}
lines.push(" endpoints: {");
for (const [tag] of tagModules) {
lines.push(` ...${toCamelCase(tag)}Module,`);
}
lines.push(" },");
lines.push("});");
lines.push("");
for (const [tag] of tagModules) {
lines.push(`export * from "./${tag.toLowerCase().replace(/[^a-z0-9]/g, "-")}.js";`);
}
return lines.join("\n");
}
// src/core/codegen.ts
var CodegenCore = class {

@@ -1022,2 +1586,5 @@ constructor(config) {

async execute() {
if (this.config.swaggerSourcePath) {
await this.generateConfigFromSwagger();
}
const apiConfig = await this.loadAPIConfig();

@@ -1031,2 +1598,63 @@ this.config.apiConfig = apiConfig;

}
/**
* Generate single config file (no split)
*/
async generateSingleConfigFile(endpoints, spec) {
const configContent = generateConfigFile(endpoints, spec, this.config.baseUrl);
const endpointsDir = path12.dirname(this.config.endpointsPath);
await fs11.mkdir(endpointsDir, { recursive: true });
await fs11.writeFile(this.config.endpointsPath, configContent, "utf-8");
console.log(`\u2713 Generated endpoint config at ${this.config.endpointsPath}`);
}
/**
* Generate multiple module files split by tags
*/
async generateMultipleModuleFiles(endpoints, spec) {
const grouped = groupEndpointsByTags(endpoints);
console.log(`\u2713 Grouped into ${grouped.size} modules by tags`);
const endpointsDir = path12.dirname(this.config.endpointsPath);
const modulesDir = path12.join(endpointsDir, "modules");
await fs11.mkdir(modulesDir, { recursive: true });
for (const [tag, tagEndpoints] of grouped.entries()) {
const moduleFileName = tag.toLowerCase().replace(/[^a-z0-9]/g, "-");
const moduleFilePath = path12.join(modulesDir, `${moduleFileName}.ts`);
const moduleContent = generateModuleFile(tag, tagEndpoints, spec);
await fs11.writeFile(moduleFilePath, moduleContent, "utf-8");
console.log(` \u2713 ${tag}: ${tagEndpoints.length} endpoints \u2192 ${moduleFileName}.ts`);
}
const indexContent = generateIndexFile(grouped, spec, this.config.baseUrl);
const indexPath = path12.join(modulesDir, "index.ts");
await fs11.writeFile(indexPath, indexContent, "utf-8");
console.log(`\u2713 Generated index.ts at ${modulesDir}/index.ts`);
const mainExportContent = `export * from "./modules/index.js";
`;
await fs11.mkdir(endpointsDir, { recursive: true });
await fs11.writeFile(this.config.endpointsPath, mainExportContent, "utf-8");
console.log(`\u2713 Generated main export at ${this.config.endpointsPath}`);
}
/**
* Generate endpoint config file from Swagger/OpenAPI spec
*/
async generateConfigFromSwagger() {
if (!this.config.swaggerSourcePath) {
return;
}
try {
console.log(`\u{1F4C4} Parsing Swagger spec from ${this.config.swaggerSourcePath}...`);
const spec = await parseOpenAPISpec(this.config.swaggerSourcePath);
console.log(`\u2713 Found OpenAPI ${spec.openapi} specification`);
console.log(` Title: ${spec.info.title} (v${spec.info.version})`);
const endpoints = extractEndpoints(spec);
console.log(`\u2713 Extracted ${endpoints.length} endpoints`);
if (this.config.splitByTags) {
await this.generateMultipleModuleFiles(endpoints, spec);
} else {
await this.generateSingleConfigFile(endpoints, spec);
}
} catch (error) {
throw new Error(
`Failed to generate config from Swagger: ${error instanceof Error ? error.message : String(error)}`
);
}
}
async loadAPIConfig() {

@@ -1057,3 +1685,3 @@ try {

// src/cli.ts
import path11 from "path";
import path13 from "path";
var program = new Command();

@@ -1065,3 +1693,3 @@ setupCLIProgram(program);

CodegenCore,
pathToFileURL: (filePath) => new URL(`file://${path11.resolve(filePath)}`)
pathToFileURL: (filePath) => new URL(`file://${path13.resolve(filePath)}`)
};

@@ -1068,0 +1696,0 @@ setupGenerateCommand(program, context);

@@ -20,2 +20,14 @@ import { APIConfig } from '@cushin/api-runtime';

/**
* Path to Swagger/OpenAPI specification file (JSON or YAML)
* When provided, endpoints will be generated from this spec
* @optional
*/
swaggerSource?: string;
/**
* Split generated endpoints into separate files by tags
* When true with swaggerSource, each tag becomes a separate module file
* @default false
*/
splitByTags?: boolean;
/**
* Provider type: 'vite' | 'nextjs'

@@ -83,2 +95,3 @@ */

outputDir: string;
swaggerSourcePath?: string;
apiConfig?: APIConfig;

@@ -96,2 +109,14 @@ }

execute(): Promise<void>;
/**
* Generate single config file (no split)
*/
private generateSingleConfigFile;
/**
* Generate multiple module files split by tags
*/
private generateMultipleModuleFiles;
/**
* Generate endpoint config file from Swagger/OpenAPI spec
*/
private generateConfigFromSwagger;
private loadAPIConfig;

@@ -98,0 +123,0 @@ }

@@ -28,2 +28,3 @@ // src/index.ts

const outputDir = path.resolve(rootDir, userConfig.output);
const swaggerSourcePath = userConfig.swaggerSource ? path.resolve(rootDir, userConfig.swaggerSource) : void 0;
const generateHooks = userConfig.generateHooks ?? true;

@@ -39,2 +40,3 @@ const generateServerActions = userConfig.generateServerActions ?? userConfig.provider === "nextjs";

outputDir,
swaggerSourcePath,
generateHooks,

@@ -1013,2 +1015,564 @@ generateServerActions,

import { fileURLToPath } from "url";
import fs11 from "fs/promises";
import path12 from "path";
// src/swagger/parser.ts
import fs10 from "fs";
import path11 from "path";
async function parseOpenAPISpec(filePath) {
const content = await fs10.promises.readFile(filePath, "utf-8");
const ext = path11.extname(filePath).toLowerCase();
let spec;
if (ext === ".json") {
spec = JSON.parse(content);
} else if (ext === ".yaml" || ext === ".yml") {
const yaml = await import("yaml");
spec = yaml.parse(content);
} else {
throw new Error(
`Unsupported file format: ${ext}. Only .json, .yaml, and .yml are supported.`
);
}
if (!spec.openapi || !spec.openapi.startsWith("3.")) {
throw new Error(
`Unsupported OpenAPI version: ${spec.openapi}. Only OpenAPI 3.0 and 3.1 are supported.`
);
}
return spec;
}
function extractEndpoints(spec) {
const endpoints = [];
for (const [path13, pathItem] of Object.entries(spec.paths)) {
const methods = [
"get",
"post",
"put",
"delete",
"patch",
"head",
"options"
];
for (const method of methods) {
const operation = pathItem[method];
if (!operation) continue;
const endpoint = parseOperation(
path13,
method,
operation,
pathItem,
spec
);
endpoints.push(endpoint);
}
}
return endpoints;
}
function parseOperation(path13, method, operation, pathItem, spec) {
const allParameters = [
...pathItem.parameters || [],
...operation.parameters || []
];
const pathParams = [];
const queryParams = [];
for (const param of allParameters) {
const resolved = resolveParameter(param, spec);
const parsed = {
name: resolved.name,
type: schemaToTypeString(resolved.schema),
required: resolved.required ?? resolved.in === "path",
description: resolved.description,
schema: resolved.schema
// Keep original schema
};
if (resolved.in === "path") {
pathParams.push(parsed);
} else if (resolved.in === "query") {
queryParams.push(parsed);
}
}
let requestBody;
if (operation.requestBody) {
const resolved = resolveRequestBody(operation.requestBody, spec);
const content = resolved.content?.["application/json"];
if (content) {
requestBody = {
type: schemaToTypeString(content.schema),
required: resolved.required ?? false,
description: resolved.description,
schema: content.schema
};
}
}
let response;
const responses = operation.responses;
const successStatus = responses["200"] || responses["201"] || responses["204"];
if (successStatus) {
const statusCode = responses["200"] ? "200" : responses["201"] ? "201" : "204";
const resolved = resolveResponse(successStatus, spec);
const content = resolved.content?.["application/json"];
if (content || statusCode === "204") {
response = {
statusCode,
type: content ? schemaToTypeString(content.schema) : "void",
description: resolved.description,
schema: content?.schema || { type: "null" }
};
}
}
if (!response) {
for (const [statusCode, res] of Object.entries(responses)) {
if (statusCode.startsWith("2")) {
const resolved = resolveResponse(res, spec);
const content = resolved.content?.["application/json"];
response = {
statusCode,
type: content ? schemaToTypeString(content.schema) : "void",
description: resolved.description,
schema: content?.schema || { type: "null" }
};
break;
}
}
}
return {
path: path13,
method: method.toUpperCase(),
operationId: operation.operationId,
summary: operation.summary,
description: operation.description,
tags: operation.tags,
pathParams: pathParams.length > 0 ? pathParams : void 0,
queryParams: queryParams.length > 0 ? queryParams : void 0,
requestBody,
response
};
}
function resolveParameter(param, spec) {
if ("$ref" in param && param.$ref) {
const refPath = param.$ref.replace("#/components/parameters/", "");
const resolved = spec.components?.parameters?.[refPath];
if (!resolved) {
throw new Error(`Cannot resolve parameter reference: ${param.$ref}`);
}
return resolved;
}
return param;
}
function resolveRequestBody(requestBody, spec) {
if ("$ref" in requestBody && requestBody.$ref) {
const refPath = requestBody.$ref.replace("#/components/requestBodies/", "");
const resolved = spec.components?.requestBodies?.[refPath];
if (!resolved) {
throw new Error(`Cannot resolve request body reference: ${requestBody.$ref}`);
}
return resolved;
}
return requestBody;
}
function resolveResponse(response, spec) {
if ("$ref" in response && response.$ref) {
const refPath = response.$ref.replace("#/components/responses/", "");
const resolved = spec.components?.responses?.[refPath];
if (!resolved) {
throw new Error(`Cannot resolve response reference: ${response.$ref}`);
}
return resolved;
}
return response;
}
function schemaToTypeString(schema) {
if (!schema) return "any";
if (schema.$ref) {
const parts = schema.$ref.split("/");
return parts[parts.length - 1];
}
if (schema.type) {
switch (schema.type) {
case "string":
return "string";
case "number":
case "integer":
return "number";
case "boolean":
return "boolean";
case "array":
if (schema.items) {
return `${schemaToTypeString(schema.items)}[]`;
}
return "any[]";
case "object":
return "object";
default:
return "any";
}
}
if (schema.allOf) {
return schema.allOf.map(schemaToTypeString).join(" & ");
}
if (schema.oneOf || schema.anyOf) {
const schemas = schema.oneOf || schema.anyOf;
return schemas.map(schemaToTypeString).join(" | ");
}
return "any";
}
// src/swagger/config-generator.ts
function groupEndpointsByTags(endpoints) {
const grouped = /* @__PURE__ */ new Map();
for (const endpoint of endpoints) {
const tag = endpoint.tags && endpoint.tags.length > 0 ? endpoint.tags[0] : "default";
if (!grouped.has(tag)) {
grouped.set(tag, []);
}
grouped.get(tag).push(endpoint);
}
return grouped;
}
function generateConfigFile(endpoints, spec, baseUrl) {
const lines = [];
lines.push('import { defineConfig, defineEndpoint } from "@cushin/api-runtime";');
lines.push('import { z } from "zod";');
lines.push("");
if (spec.components?.schemas) {
lines.push("// Schema definitions from OpenAPI components");
for (const [name, schema] of Object.entries(spec.components.schemas)) {
const zodSchema = convertSchemaToZod(schema, spec);
lines.push(`const ${name}Schema = ${zodSchema};`);
}
lines.push("");
}
lines.push("// Endpoint definitions");
const endpointNames = [];
for (const endpoint of endpoints) {
const name = generateEndpointName(endpoint);
endpointNames.push(name);
const path13 = convertPathFormat(endpoint.path);
lines.push(`const ${name} = defineEndpoint({`);
lines.push(` path: "${path13}",`);
lines.push(` method: "${endpoint.method}",`);
if (endpoint.pathParams && endpoint.pathParams.length > 0) {
const paramsSchema = generateParamsSchema(endpoint.pathParams, spec);
lines.push(` params: ${paramsSchema},`);
}
if (endpoint.queryParams && endpoint.queryParams.length > 0) {
const querySchema = generateQuerySchema(endpoint.queryParams, spec);
lines.push(` query: ${querySchema},`);
}
if (endpoint.requestBody) {
const bodySchema = convertSchemaToZod(endpoint.requestBody.schema, spec);
lines.push(` body: ${bodySchema},`);
}
if (endpoint.response) {
const responseSchema = convertSchemaToZod(endpoint.response.schema, spec);
lines.push(` response: ${responseSchema},`);
} else {
lines.push(` response: z.any(),`);
}
if (endpoint.tags && endpoint.tags.length > 0) {
const tagsStr = endpoint.tags.map((t) => `"${t}"`).join(", ");
lines.push(` tags: [${tagsStr}],`);
}
if (endpoint.description || endpoint.summary) {
const desc = endpoint.description || endpoint.summary;
lines.push(` description: "${escapeString(desc)}",`);
}
lines.push("});");
lines.push("");
}
lines.push("// API Configuration");
lines.push("export const apiConfig = defineConfig({");
if (baseUrl) {
lines.push(` baseUrl: "${baseUrl}",`);
} else if (spec.servers && spec.servers.length > 0) {
lines.push(` baseUrl: "${spec.servers[0].url}",`);
}
lines.push(" endpoints: {");
for (const name of endpointNames) {
lines.push(` ${name},`);
}
lines.push(" },");
lines.push("});");
lines.push("");
return lines.join("\n");
}
function generateEndpointName(endpoint) {
if (endpoint.operationId) {
return toCamelCase(endpoint.operationId);
}
const method = endpoint.method.toLowerCase();
const pathParts = endpoint.path.split("/").filter((p) => p && !p.startsWith(":")).map((p) => p.replace(/[^a-zA-Z0-9]/g, ""));
const parts = [method, ...pathParts];
return toCamelCase(parts.join("_"));
}
function toCamelCase(str) {
return str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toLowerCase());
}
function generateParamsSchema(params, spec) {
const props = [];
for (const param of params) {
const zodType = convertSchemaToZod(param.schema, spec);
props.push(` ${param.name}: ${zodType}`);
}
return `z.object({
${props.join(",\n")}
})`;
}
function generateQuerySchema(params, spec) {
const props = [];
for (const param of params) {
let zodType = convertSchemaToZod(param.schema, spec);
if (!param.required) {
zodType += ".optional()";
}
props.push(` ${param.name}: ${zodType}`);
}
return `z.object({
${props.join(",\n")}
})`;
}
function convertSchemaToZod(schema, spec) {
if (!schema) return "z.any()";
if (schema.$ref) {
const refName = schema.$ref.split("/").pop();
if (refName && spec.components?.schemas?.[refName]) {
return `${refName}Schema`;
}
return "z.any()";
}
if (schema.type) {
switch (schema.type) {
case "string":
if (schema.enum) {
const values = schema.enum.map((v) => `"${v}"`).join(", ");
return `z.enum([${values}])`;
}
return "z.string()";
case "number":
case "integer":
return "z.number()";
case "boolean":
return "z.boolean()";
case "array":
if (schema.items) {
const itemSchema = convertSchemaToZod(schema.items, spec);
return `z.array(${itemSchema})`;
}
return "z.array(z.any())";
case "object":
if (schema.properties) {
const props = [];
const required = schema.required || [];
for (const [propName, propSchema] of Object.entries(
schema.properties
)) {
let zodType = convertSchemaToZod(propSchema, spec);
if (!required.includes(propName)) {
zodType += ".optional()";
}
props.push(` ${propName}: ${zodType}`);
}
return `z.object({
${props.join(",\n")}
})`;
}
return "z.object({})";
case "null":
return "z.null()";
default:
return "z.any()";
}
}
if (schema.allOf) {
const schemas = schema.allOf.map((s) => convertSchemaToZod(s, spec));
return schemas.join(".and(");
}
if (schema.oneOf || schema.anyOf) {
const schemas = (schema.oneOf || schema.anyOf).map(
(s) => convertSchemaToZod(s, spec)
);
return `z.union([${schemas.join(", ")}])`;
}
return "z.any()";
}
function escapeString(str) {
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
}
function convertPathFormat(path13) {
return path13.replace(/\{([^}]+)\}/g, ":$1");
}
function generateModuleFile(tag, endpoints, spec) {
const lines = [];
lines.push('import { defineEndpoint } from "@cushin/api-runtime";');
lines.push('import { z } from "zod";');
lines.push("");
const usedSchemas = /* @__PURE__ */ new Set();
for (const endpoint of endpoints) {
collectUsedSchemas(endpoint, usedSchemas, spec);
}
if (usedSchemas.size > 0 && spec.components?.schemas) {
lines.push("// Schema definitions");
const sortedSchemas = sortSchemasByDependencies(Array.from(usedSchemas), spec);
for (const schemaName of sortedSchemas) {
const schema = spec.components.schemas[schemaName];
if (schema) {
const zodSchema = convertSchemaToZod(schema, spec);
lines.push(`const ${schemaName}Schema = ${zodSchema};`);
}
}
lines.push("");
}
lines.push("// Endpoint definitions");
const endpointNames = [];
for (const endpoint of endpoints) {
const name = generateEndpointName(endpoint);
endpointNames.push(name);
const path13 = convertPathFormat(endpoint.path);
lines.push(`export const ${name} = defineEndpoint({`);
lines.push(` path: "${path13}",`);
lines.push(` method: "${endpoint.method}",`);
if (endpoint.pathParams && endpoint.pathParams.length > 0) {
const paramsSchema = generateParamsSchema(endpoint.pathParams, spec);
lines.push(` params: ${paramsSchema},`);
}
if (endpoint.queryParams && endpoint.queryParams.length > 0) {
const querySchema = generateQuerySchema(endpoint.queryParams, spec);
lines.push(` query: ${querySchema},`);
}
if (endpoint.requestBody) {
const bodySchema = convertSchemaToZod(endpoint.requestBody.schema, spec);
lines.push(` body: ${bodySchema},`);
}
if (endpoint.response) {
const responseSchema = convertSchemaToZod(endpoint.response.schema, spec);
lines.push(` response: ${responseSchema},`);
} else {
lines.push(` response: z.any(),`);
}
if (endpoint.tags && endpoint.tags.length > 0) {
const tagsStr = endpoint.tags.map((t) => `"${t}"`).join(", ");
lines.push(` tags: [${tagsStr}],`);
}
if (endpoint.description || endpoint.summary) {
const desc = endpoint.description || endpoint.summary;
lines.push(` description: "${escapeString(desc)}",`);
}
lines.push("});");
lines.push("");
}
return lines.join("\n");
}
function sortSchemasByDependencies(schemaNames, spec) {
const sorted = [];
const visited = /* @__PURE__ */ new Set();
const visiting = /* @__PURE__ */ new Set();
function visit(name) {
if (visited.has(name)) return;
if (visiting.has(name)) {
return;
}
visiting.add(name);
const schema = spec.components?.schemas?.[name];
if (schema) {
const deps = /* @__PURE__ */ new Set();
extractSchemaNames(schema, deps, spec);
for (const dep of deps) {
if (dep !== name && schemaNames.includes(dep)) {
visit(dep);
}
}
}
visiting.delete(name);
if (!visited.has(name)) {
visited.add(name);
sorted.push(name);
}
}
for (const name of schemaNames) {
visit(name);
}
return sorted;
}
function collectUsedSchemas(endpoint, usedSchemas, spec) {
if (endpoint.requestBody?.schema) {
extractSchemaNames(endpoint.requestBody.schema, usedSchemas, spec);
}
if (endpoint.response?.schema) {
extractSchemaNames(endpoint.response.schema, usedSchemas, spec);
}
if (endpoint.pathParams) {
for (const param of endpoint.pathParams) {
extractSchemaNames(param.schema, usedSchemas, spec);
}
}
if (endpoint.queryParams) {
for (const param of endpoint.queryParams) {
extractSchemaNames(param.schema, usedSchemas, spec);
}
}
}
function extractSchemaNames(schema, names, spec) {
if (!schema) return;
if (schema.$ref) {
const schemaName = schema.$ref.split("/").pop();
if (schemaName) {
names.add(schemaName);
const referencedSchema = spec.components?.schemas?.[schemaName];
if (referencedSchema) {
extractSchemaNames(referencedSchema, names, spec);
}
}
return;
}
if (schema.properties) {
for (const prop of Object.values(schema.properties)) {
extractSchemaNames(prop, names, spec);
}
}
if (schema.items) {
extractSchemaNames(schema.items, names, spec);
}
if (schema.allOf) {
for (const s of schema.allOf) {
extractSchemaNames(s, names, spec);
}
}
if (schema.oneOf) {
for (const s of schema.oneOf) {
extractSchemaNames(s, names, spec);
}
}
if (schema.anyOf) {
for (const s of schema.anyOf) {
extractSchemaNames(s, names, spec);
}
}
}
function generateIndexFile(tagModules, spec, baseUrl) {
const lines = [];
lines.push('import { defineConfig } from "@cushin/api-runtime";');
lines.push("");
for (const [tag] of tagModules) {
const moduleFileName = tag.toLowerCase().replace(/[^a-z0-9]/g, "-");
lines.push(`import * as ${toCamelCase(tag)}Module from "./${moduleFileName}.js";`);
}
lines.push("");
lines.push("export const apiConfig = defineConfig({");
const url = baseUrl || (spec.servers && spec.servers.length > 0 ? spec.servers[0].url : void 0);
if (url) {
lines.push(` baseUrl: "${url}",`);
}
lines.push(" endpoints: {");
for (const [tag] of tagModules) {
lines.push(` ...${toCamelCase(tag)}Module,`);
}
lines.push(" },");
lines.push("});");
lines.push("");
for (const [tag] of tagModules) {
lines.push(`export * from "./${tag.toLowerCase().replace(/[^a-z0-9]/g, "-")}.js";`);
}
return lines.join("\n");
}
// src/core/codegen.ts
var CodegenCore = class {

@@ -1019,2 +1583,5 @@ constructor(config) {

async execute() {
if (this.config.swaggerSourcePath) {
await this.generateConfigFromSwagger();
}
const apiConfig = await this.loadAPIConfig();

@@ -1028,2 +1595,63 @@ this.config.apiConfig = apiConfig;

}
/**
* Generate single config file (no split)
*/
async generateSingleConfigFile(endpoints, spec) {
const configContent = generateConfigFile(endpoints, spec, this.config.baseUrl);
const endpointsDir = path12.dirname(this.config.endpointsPath);
await fs11.mkdir(endpointsDir, { recursive: true });
await fs11.writeFile(this.config.endpointsPath, configContent, "utf-8");
console.log(`\u2713 Generated endpoint config at ${this.config.endpointsPath}`);
}
/**
* Generate multiple module files split by tags
*/
async generateMultipleModuleFiles(endpoints, spec) {
const grouped = groupEndpointsByTags(endpoints);
console.log(`\u2713 Grouped into ${grouped.size} modules by tags`);
const endpointsDir = path12.dirname(this.config.endpointsPath);
const modulesDir = path12.join(endpointsDir, "modules");
await fs11.mkdir(modulesDir, { recursive: true });
for (const [tag, tagEndpoints] of grouped.entries()) {
const moduleFileName = tag.toLowerCase().replace(/[^a-z0-9]/g, "-");
const moduleFilePath = path12.join(modulesDir, `${moduleFileName}.ts`);
const moduleContent = generateModuleFile(tag, tagEndpoints, spec);
await fs11.writeFile(moduleFilePath, moduleContent, "utf-8");
console.log(` \u2713 ${tag}: ${tagEndpoints.length} endpoints \u2192 ${moduleFileName}.ts`);
}
const indexContent = generateIndexFile(grouped, spec, this.config.baseUrl);
const indexPath = path12.join(modulesDir, "index.ts");
await fs11.writeFile(indexPath, indexContent, "utf-8");
console.log(`\u2713 Generated index.ts at ${modulesDir}/index.ts`);
const mainExportContent = `export * from "./modules/index.js";
`;
await fs11.mkdir(endpointsDir, { recursive: true });
await fs11.writeFile(this.config.endpointsPath, mainExportContent, "utf-8");
console.log(`\u2713 Generated main export at ${this.config.endpointsPath}`);
}
/**
* Generate endpoint config file from Swagger/OpenAPI spec
*/
async generateConfigFromSwagger() {
if (!this.config.swaggerSourcePath) {
return;
}
try {
console.log(`\u{1F4C4} Parsing Swagger spec from ${this.config.swaggerSourcePath}...`);
const spec = await parseOpenAPISpec(this.config.swaggerSourcePath);
console.log(`\u2713 Found OpenAPI ${spec.openapi} specification`);
console.log(` Title: ${spec.info.title} (v${spec.info.version})`);
const endpoints = extractEndpoints(spec);
console.log(`\u2713 Extracted ${endpoints.length} endpoints`);
if (this.config.splitByTags) {
await this.generateMultipleModuleFiles(endpoints, spec);
} else {
await this.generateSingleConfigFile(endpoints, spec);
}
} catch (error) {
throw new Error(
`Failed to generate config from Swagger: ${error instanceof Error ? error.message : String(error)}`
);
}
}
async loadAPIConfig() {

@@ -1030,0 +1658,0 @@ try {

+3
-2
{
"name": "@cushin/api-codegen",
"version": "5.0.8",
"version": "6.0.0",
"description": "Type-safe API client generator for React/Next.js with automatic hooks and server actions generation",

@@ -63,3 +63,4 @@ "type": "module",

"ora": "^8.0.0",
"@cushin/api-runtime": "5.0.8",
"yaml": "^2.8.2",
"@cushin/api-runtime": "5.0.9",
"@cushin/codegen-cli": "5.0.0"

@@ -66,0 +67,0 @@ },

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display