czh-api 1.0.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/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # CZH API
2
+
3
+ `czh-api` 是一款功能强大且灵活的前端 API 同步工具,灵感来源于 Pont。它能够根据 Swagger/OpenAPI 文档自动生成 TypeScript API 代码,帮助您保持前后端接口的一致性,提高开发效率,并减少手动编写代码带来的错误。
4
+
5
+ ## ✨ 功能特性
6
+
7
+ - **多标准支持**: 完美兼容 Swagger v2, OpenAPI v3, 以及 Knife4j 风格的 JSON 文档。
8
+ - **智能模块生成**: 根据接口的 `x-package` 字段(例如 `com.czh.SysConfigController` -> `sysConfig` 模块)自动对 API 进行模块化分组,当该字段缺失时,则使用 URL 的第一段作为备用方案。
9
+ - **清晰的命名规范**: API 函数名采用 `HTTP方法` + `驼峰式路径` 的方式生成(例如 `POST /sys/user/add` -> `postSysUserAdd`),确保了全局唯一性和代码的可读性。
10
+ - **可定制化模板**: 提供 Handlebars (`.hbs`) 模板,覆盖 API、类型和模块索引文件,允许您完全自定义生成的代码风格。
11
+ - **自动化类型生成**: 为所有请求参数和响应数据生成 TypeScript 类型定义(`types.ts`)。
12
+ - **详尽的 JSDoc 注释**: 为每个 API 函数自动生成全面的 JSDoc 注释,包含接口描述以及每个参数详尽的 `@param` 注解, 参数名称带[]则后端为非必填。
13
+ - **灵活的配置**: 使用 `czh-api.config.json` 文件来管理所有设置,例如 Swagger 地址、输出目录、需要排除的路径以及自定义导入语句等。
14
+ - **NPM 包与命令行工具**: 已封装为 Node.js 命令行工具,便于在任何前端项目中安装和使用。
15
+
16
+ ## 🚀 安装
17
+
18
+ 通过 npm 全局安装本工具:
19
+
20
+ ```bash
21
+ npm install -g czh-api
22
+ ```
23
+
24
+ ## 🛠️ 使用说明
25
+
26
+ ### 主要命令
27
+
28
+ `czh-api` 提供了以下核心命令来管理您的 API 代码生成:
29
+
30
+ | 命令 | 别名 (`alias`) | 描述 |
31
+ |---------------|----------------|-----------------------------------------------------------------------------------------------------------------------------------|
32
+ | `czh-api` | `-h`, `--help` | **显示帮助信息**。列出所有可用的命令和选项。 |
33
+ | `czh-api -v`| `--version` | **查看版本号**。显示当前安装的 `czh-api` 的版本。 |
34
+ | `czh-api init` | | **初始化项目**。在当前目录下创建 `czh-api.config.json` 配置文件和 `czh-api-template` 模板文件夹。 |
35
+ | `czh-api build` | | **生成 API 代码**。根据配置文件中的 `url` 获取远程 API 文档,并在指定的 `outputDir` 目录中生成所有 TypeScript 模块、类型和索引文件。 |
36
+
37
+ ### 配置文件 (`czh-api.config.json`)
38
+
39
+ `init` 命令会生成此文件。您需要根据项目需求进行编辑:
40
+
41
+ ```json
42
+ {
43
+ "url": "https://your-swagger-docs/v2/api-docs",
44
+ "outputDir": "./src/api",
45
+ "templates": "./czh-api-template",
46
+ "customImports": [
47
+ "import http from '@/utils/http';"
48
+ ],
49
+ "excludePaths": [
50
+ "/sys/log",
51
+ "/tool/gen"
52
+ ]
53
+ }
54
+ ```
55
+
56
+ **配置项说明:**
57
+
58
+ | 选项 | 类型 | 是否必须 | 描述 |
59
+ |------------------|------------|----------|-------------------------------------------------------------------------|
60
+ | `url` | `string` | 是 | 您的 Swagger/OpenAPI JSON 文档地址。 |
61
+ | `outputDir` | `string` | 是 | 用于存放生成的 API 模块的目录。 |
62
+ | `templates` | `string` | 否 | 指向您的自定义模板目录的路径。默认为 `init` 命令生成的目录。 |
63
+ | `customImports` | `string[]` | 否 | 一个自定义导入语句的数组,会被添加到每个生成的 API 文件的顶部。 |
64
+ | `excludePaths` | `string[]` | 否 | 一个 URL 路径前缀的数组。任何以此数组中前缀开头的 API 都将被忽略。 |
65
+
66
+
67
+ ## 👨‍💻 开发者指南
68
+
69
+ 如果您克隆了本仓库并希望进行二次开发、贡献或发布您自己的版本,请遵循以下步骤:
70
+
71
+ ### 1. 安装依赖
72
+ 在项目根目录下运行,安装所有开发和运行时所需的依赖。
73
+ ```bash
74
+ npm install
75
+ ```
76
+
77
+ ### 2. 编译源码
78
+ 此命令会将 `src/` 目录下的 TypeScript 源码编译成 JavaScript,并输出到 `dist/` 目录。同时,它会自动复制必要的模板文件。
79
+ ```bash
80
+ npm run build
81
+ ```
82
+
83
+ ### 3. 本地测试
84
+ 使用 `npm link` 可以将本地开发版本的包链接到全局,让您可以在任何地方通过 `czh-api` 命令来测试您的修改,而无需发布到 NPM。
85
+ ```bash
86
+ npm link
87
+ ```
88
+
89
+ ### 4. 发布到 NPM
90
+ 当您准备好发布新版本时,请执行此命令。
91
+ **注意**: 在发布前,请确保您已登录 NPM (`npm login`),并在 `package.json` 中更新了包的版本号。
92
+ ```bash
93
+ npm publish
94
+ ```
95
+
96
+ ### 5. 解除本地链接/卸载
97
+ 如果您想解除本地的链接状态,可以使用 `unlink` 命令。如果您想从全局卸载,请使用 `uninstall`。
98
+ ```bash
99
+ # 解除本地开发链接
100
+ npm unlink czh-api
101
+
102
+ # 或,全局卸载包
103
+ npm uninstall -g czh-api
104
+ ```
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.handleBuild = void 0;
16
+ /*
17
+ * @description:
18
+ * @Author: czh
19
+ * @Date: 2025-07-02 10:39:30
20
+ * @LastEditors: Czh
21
+ * @LastEditTime: 2025-07-02 11:47:00
22
+ */
23
+ const chalk_1 = __importDefault(require("chalk"));
24
+ const path_1 = __importDefault(require("path"));
25
+ const fs_1 = __importDefault(require("fs"));
26
+ const SwaggerParser = require('swagger-parser');
27
+ const parser_1 = require("../core/parser");
28
+ const handlebars_1 = __importDefault(require("handlebars"));
29
+ const rimraf_1 = require("rimraf");
30
+ const axios_1 = __importDefault(require("axios"));
31
+ const json_schema_to_typescript_1 = require("json-schema-to-typescript");
32
+ const handleBuild = () => __awaiter(void 0, void 0, void 0, function* () {
33
+ try {
34
+ console.log(chalk_1.default.blue('Building API from source...'));
35
+ // Load config
36
+ const configPath = path_1.default.join(process.cwd(), 'czh-api.config.json');
37
+ if (!fs_1.default.existsSync(configPath)) {
38
+ console.error(chalk_1.default.red('错误:找不到 czh-api.config.json 配置文件。请先运行 `czh-api init`。'));
39
+ process.exit(1);
40
+ }
41
+ const config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf-8'));
42
+ // Fetch and parse Swagger/OpenAPI document
43
+ console.log(chalk_1.default.blue(`正在从 ${config.url} 获取 API 文档...`));
44
+ const response = yield axios_1.default.get(config.url);
45
+ const api = yield SwaggerParser.bundle(response.data);
46
+ console.log(chalk_1.default.green('Swagger document fetched and bundled successfully.'));
47
+ // Process the API document
48
+ const modules = (0, parser_1.processApi)(api, config.excludePaths || []);
49
+ console.log(chalk_1.default.blue('API processed into modules.'));
50
+ // Clean output directory
51
+ const outputDir = path_1.default.join(process.cwd(), config.outputDir);
52
+ if (fs_1.default.existsSync(outputDir)) {
53
+ yield (0, rimraf_1.rimraf)(outputDir);
54
+ }
55
+ fs_1.default.mkdirSync(outputDir, { recursive: true });
56
+ console.log(chalk_1.default.yellow(`Cleaned output directory: ${outputDir}`));
57
+ const readTemplate = (templatePath, templateName) => {
58
+ const defaultPath = path_1.default.resolve(__dirname, '../templates', templateName);
59
+ const userPath = templatePath ? path_1.default.join(process.cwd(), templatePath, templateName) : defaultPath;
60
+ try {
61
+ return fs_1.default.readFileSync(userPath, 'utf-8');
62
+ }
63
+ catch (error) {
64
+ return fs_1.default.readFileSync(defaultPath, 'utf-8');
65
+ }
66
+ };
67
+ const apiTemplateStr = readTemplate(config.templates, 'api.hbs');
68
+ const apiTemplate = handlebars_1.default.compile(apiTemplateStr);
69
+ for (const moduleName in modules) {
70
+ const module = modules[moduleName];
71
+ const moduleDir = path_1.default.join(outputDir, moduleName);
72
+ fs_1.default.mkdirSync(moduleDir, { recursive: true });
73
+ let typesContent = '';
74
+ // Generate types.ts
75
+ if (Object.keys(module.schemas).length > 0) {
76
+ const schemasWithTitles = {};
77
+ for (const schemaName in module.schemas) {
78
+ schemasWithTitles[schemaName] = Object.assign(Object.assign({}, module.schemas[schemaName]), { title: schemaName });
79
+ }
80
+ const rootSchemaForCompiler = {
81
+ title: 'schemas',
82
+ type: 'object',
83
+ properties: {},
84
+ additionalProperties: false,
85
+ definitions: schemasWithTitles,
86
+ components: {
87
+ schemas: schemasWithTitles
88
+ }
89
+ };
90
+ typesContent = yield (0, json_schema_to_typescript_1.compile)(rootSchemaForCompiler, 'schemas', {
91
+ bannerComment: '/* eslint-disable */\n/**\n* This file was automatically generated by czh-api.\n* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,\n* and run czh-api build to regenerate this file.\n*/',
92
+ unreachableDefinitions: true,
93
+ additionalProperties: false,
94
+ });
95
+ typesContent = typesContent.replace(/export interface \w+ \{\s*\}\n/g, '');
96
+ const refCommentRegex = /\/\*\*\n \* This interface was referenced by `\w+`'s JSON-Schema[\s\S]*?\*\/\n/g;
97
+ typesContent = typesContent.replace(refCommentRegex, '');
98
+ typesContent = typesContent.replace(/:\s*\{\}\[\]/g, ': any[]');
99
+ typesContent = typesContent.replace(/\[k: string\]: \{\}/g, '[k: string]: any');
100
+ const inlineIndexSignatureRegex = /:\s*\{\s*\/\*\*[\s\S]*?\*\/[\s\n\r]*\[k: string\]: any;\s*\};/g;
101
+ typesContent = typesContent.replace(inlineIndexSignatureRegex, ': any;');
102
+ typesContent = typesContent.replace(/: \{\}/g, ': any');
103
+ typesContent = typesContent.replace(/\}\n/g, '}\n\n');
104
+ fs_1.default.writeFileSync(path_1.default.join(moduleDir, 'types.ts'), typesContent);
105
+ }
106
+ // Generate API file
107
+ const allReferencedTypes = [...new Set(module.endpoints.flatMap(e => e.referencedTypes))];
108
+ const customImports = config.customImports || [`import http from "${config.httpClientPath}";`];
109
+ let apiFileContent = '';
110
+ if (module.description) {
111
+ apiFileContent += `/**\n * @description ${module.description}\n */\n\n`;
112
+ }
113
+ apiFileContent += `${customImports.join('\n')}\n`;
114
+ if (allReferencedTypes.length > 0) {
115
+ apiFileContent += `import type { ${allReferencedTypes.join(', ')} } from './types';\n\n`;
116
+ }
117
+ apiFileContent += module.endpoints.map(endpoint => apiTemplate(endpoint)).join('\n\n');
118
+ fs_1.default.writeFileSync(path_1.default.join(moduleDir, `${moduleName}.ts`), apiFileContent);
119
+ // Generate index.ts
120
+ const exportTypes = typesContent ? `\nexport * from './types';` : '';
121
+ fs_1.default.writeFileSync(path_1.default.join(moduleDir, 'index.ts'), `export * from './${moduleName}';${exportTypes}\n`);
122
+ }
123
+ console.log(chalk_1.default.green.bold('API code generated successfully!'));
124
+ }
125
+ catch (error) {
126
+ console.error(chalk_1.default.red('An error occurred during build process:'), error);
127
+ }
128
+ });
129
+ exports.handleBuild = handleBuild;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.handleInit = void 0;
16
+ /*
17
+ * @description:
18
+ * @Author: czh
19
+ * @Date: 2025-07-02 10:39:17
20
+ * @LastEditors: Czh
21
+ * @LastEditTime: 2025-07-02 11:45:10
22
+ */
23
+ const chalk_1 = __importDefault(require("chalk"));
24
+ const promises_1 = __importDefault(require("fs/promises"));
25
+ const path_1 = __importDefault(require("path"));
26
+ const defaultConfig = {
27
+ url: 'https://petstore.swagger.io/v2/swagger.json',
28
+ outputDir: './src/api',
29
+ templates: './czh-api-template',
30
+ customImports: ['import http from "@/utils/http";'],
31
+ excludePaths: []
32
+ };
33
+ const templates = ['api.hbs', 'index.hbs', 'types.hbs'];
34
+ const handleInit = () => __awaiter(void 0, void 0, void 0, function* () {
35
+ console.log(chalk_1.default.green('Initializing czh-api...'));
36
+ const configPath = path_1.default.join(process.cwd(), 'czh-api.config.json');
37
+ const templatesPath = path_1.default.join(process.cwd(), 'czh-api-template');
38
+ try {
39
+ yield promises_1.default.access(configPath);
40
+ console.log(chalk_1.default.yellow('Config file already exists. Aborting.'));
41
+ return;
42
+ }
43
+ catch (error) {
44
+ // File doesn't exist, which is what we want.
45
+ }
46
+ try {
47
+ // Write config file
48
+ yield promises_1.default.writeFile(configPath, JSON.stringify(defaultConfig, null, 2));
49
+ console.log(chalk_1.default.cyan(`Created config file: ${configPath}`));
50
+ // Create templates directory
51
+ yield promises_1.default.mkdir(templatesPath, { recursive: true });
52
+ // Copy template files
53
+ for (const templateName of templates) {
54
+ const sourcePath = path_1.default.resolve(__dirname, '../templates', templateName);
55
+ const destPath = path_1.default.join(templatesPath, templateName);
56
+ yield promises_1.default.copyFile(sourcePath, destPath);
57
+ console.log(chalk_1.default.cyan(`Created template: ${destPath}`));
58
+ }
59
+ console.log(chalk_1.default.green.bold('Initialization complete!'));
60
+ console.log(chalk_1.default.white('You can now edit the config and templates, then run `czh-api build`.'));
61
+ }
62
+ catch (error) {
63
+ console.error(chalk_1.default.red('An error occurred during initialization:'), error);
64
+ }
65
+ });
66
+ exports.handleInit = handleInit;
@@ -0,0 +1,315 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.processApi = void 0;
4
+ function isReferenceObject(obj) {
5
+ return obj && typeof obj.$ref === 'string';
6
+ }
7
+ function getSchemaName(ref) {
8
+ return ref.split('/').pop() || '';
9
+ }
10
+ function addSchemaWithDependencies(name, module, allSchemas) {
11
+ if (module.schemas[name]) {
12
+ return; // Already added
13
+ }
14
+ const schema = allSchemas[name];
15
+ if (!schema) {
16
+ return; // Schema not found
17
+ }
18
+ module.schemas[name] = schema;
19
+ // Recursively add dependencies
20
+ JSON.stringify(schema, (key, value) => {
21
+ if (key === '$ref' && typeof value === 'string') {
22
+ const depName = getSchemaName(value);
23
+ addSchemaWithDependencies(depName, module, allSchemas);
24
+ }
25
+ return value;
26
+ });
27
+ }
28
+ function getModuleName(path) {
29
+ const parts = path.split('/').filter(p => p && !p.startsWith('{'));
30
+ return parts[0] || 'default';
31
+ }
32
+ function extractJsdocParamsFromSchema(schema, allSchemas) {
33
+ var _a;
34
+ const params = [];
35
+ if (!schema)
36
+ return params;
37
+ let targetSchema;
38
+ if (isReferenceObject(schema)) {
39
+ const schemaName = getSchemaName(schema.$ref);
40
+ targetSchema = allSchemas[schemaName];
41
+ }
42
+ else {
43
+ targetSchema = schema;
44
+ }
45
+ if (targetSchema === null || targetSchema === void 0 ? void 0 : targetSchema.properties) {
46
+ for (const propName in targetSchema.properties) {
47
+ const prop = targetSchema.properties[propName];
48
+ // 调试: 打印出当前处理的属性
49
+ // console.log(`正在处理属性: ${propName}`, '值为:', prop);
50
+ params.push({
51
+ name: propName,
52
+ type: (prop === null || prop === void 0 ? void 0 : prop.type) || 'any',
53
+ description: (prop === null || prop === void 0 ? void 0 : prop.description) || '',
54
+ required: (_a = targetSchema.required) === null || _a === void 0 ? void 0 : _a.includes(propName)
55
+ });
56
+ }
57
+ }
58
+ return params;
59
+ }
60
+ function getModuleNameFromPackage(operation) {
61
+ const xPackage = operation['x-package'];
62
+ if (typeof xPackage === 'string' && xPackage.length > 0) {
63
+ const parts = xPackage.split('.');
64
+ const lastPart = parts[parts.length - 1];
65
+ // "Controller" is 10 characters
66
+ if (lastPart && lastPart.endsWith('Controller') && lastPart.length > 10) {
67
+ const rawName = lastPart.slice(0, -10);
68
+ return rawName.charAt(0).toLowerCase() + rawName.slice(1);
69
+ }
70
+ }
71
+ return null;
72
+ }
73
+ /**
74
+ * Processes the validated OpenAPI document into a structured format for code generation.
75
+ * @param api The bundled OpenAPI document.
76
+ * @returns A structured representation of modules and their endpoints.
77
+ */
78
+ const processApi = (api, excludePaths = []) => {
79
+ var _a, _b, _c, _d, _e, _f, _g;
80
+ const modules = {};
81
+ const allSchemas = ((_a = api.components) === null || _a === void 0 ? void 0 : _a.schemas) || api.definitions || {};
82
+ const moduleFunctionNames = {};
83
+ for (const path in api.paths) {
84
+ if (excludePaths.some(exclude => path.startsWith(exclude))) {
85
+ continue;
86
+ }
87
+ const pathItem = api.paths[path];
88
+ for (const method in pathItem) {
89
+ // Filter for actual HTTP methods
90
+ if (!['get', 'post', 'put', 'delete', 'patch', 'options', 'head'].includes(method)) {
91
+ continue;
92
+ }
93
+ const operation = pathItem[method];
94
+ if (!operation.tags)
95
+ continue;
96
+ const moduleNameFromPackage = getModuleNameFromPackage(operation);
97
+ const moduleName = moduleNameFromPackage || getModuleName(path);
98
+ if (!modules[moduleName]) {
99
+ modules[moduleName] = {
100
+ name: moduleName,
101
+ endpoints: [],
102
+ schemas: {},
103
+ description: (operation.tags && operation.tags.length > 0) ? operation.tags[0] : '',
104
+ };
105
+ moduleFunctionNames[moduleName] = new Set();
106
+ }
107
+ // Generate function name from method and path
108
+ const pathParts = path.split('/').filter(p => p && !p.startsWith('{'));
109
+ const capitalizedPathParts = pathParts.map(part => part.split(/[._-]/)
110
+ .map(subPart => subPart.charAt(0).toUpperCase() + subPart.slice(1))
111
+ .join(''));
112
+ const functionNameBase = `${method}${capitalizedPathParts.join('')}`;
113
+ let functionName = functionNameBase;
114
+ let counter = 1;
115
+ while (moduleFunctionNames[moduleName].has(functionName)) {
116
+ functionName = `${functionNameBase}_${counter}`;
117
+ counter++;
118
+ }
119
+ moduleFunctionNames[moduleName].add(functionName);
120
+ const referencedTypes = [];
121
+ let requestParamsTypeName = undefined;
122
+ let requestBodyTypeName = undefined;
123
+ let contentType = undefined;
124
+ const jsdocParams = [];
125
+ // --- FormData Detection ---
126
+ let isFormData = false;
127
+ // Method 1: Check requestBody
128
+ if (operation.requestBody && !isReferenceObject(operation.requestBody)) {
129
+ if ((_b = operation.requestBody.content) === null || _b === void 0 ? void 0 : _b['multipart/form-data']) {
130
+ isFormData = true;
131
+ }
132
+ }
133
+ // Method 2: Check for binary formats in parameters (non-standard but common)
134
+ if (!isFormData && operation.parameters) {
135
+ if (operation.parameters.some(p => {
136
+ var _a, _b;
137
+ if (isReferenceObject(p))
138
+ return false;
139
+ // Treat as FormData if in: 'formData' is explicitly set
140
+ if (p.in === 'formData')
141
+ return true;
142
+ const schema = p.schema;
143
+ // Treat as FormData if format is binary/byte (single file)
144
+ if ((schema === null || schema === void 0 ? void 0 : schema.format) === 'binary' || (schema === null || schema === void 0 ? void 0 : schema.format) === 'byte')
145
+ return true;
146
+ // Treat as FormData if it's an array of binary/byte (multiple files)
147
+ if ((schema === null || schema === void 0 ? void 0 : schema.type) === 'array' && (((_a = schema.items) === null || _a === void 0 ? void 0 : _a.format) === 'binary' || ((_b = schema.items) === null || _b === void 0 ? void 0 : _b.format) === 'byte')) {
148
+ return true;
149
+ }
150
+ return false;
151
+ })) {
152
+ isFormData = true;
153
+ }
154
+ }
155
+ if (isFormData) {
156
+ contentType = 'multipart/form-data';
157
+ }
158
+ // --- End FormData Detection ---
159
+ // Handle All Parameters
160
+ if (operation.parameters) {
161
+ const paramsSchema = {
162
+ type: 'object',
163
+ properties: {},
164
+ required: [],
165
+ };
166
+ // For FormData, we will build a schema for the body from query/formData parameters
167
+ const formDataSchema = {
168
+ type: 'object',
169
+ properties: {},
170
+ required: [],
171
+ };
172
+ for (const param of operation.parameters) {
173
+ if (!isReferenceObject(param)) {
174
+ const paramToAdd = {
175
+ name: param.name,
176
+ description: param.description || '',
177
+ required: param.required,
178
+ type: ((_c = param.schema) === null || _c === void 0 ? void 0 : _c.type) || 'any'
179
+ };
180
+ // If the parameter's schema is a reference, expand its properties
181
+ if (param.in === 'query' && param.schema && isReferenceObject(param.schema)) {
182
+ const schemaName = getSchemaName(param.schema.$ref);
183
+ const refSchema = allSchemas[schemaName];
184
+ if (refSchema && refSchema.properties) {
185
+ if (!paramsSchema.properties)
186
+ paramsSchema.properties = {};
187
+ Object.assign(paramsSchema.properties, refSchema.properties);
188
+ if (refSchema.required) {
189
+ if (!paramsSchema.required)
190
+ paramsSchema.required = [];
191
+ paramsSchema.required.push(...refSchema.required);
192
+ }
193
+ }
194
+ }
195
+ else if (isFormData && (param.in === 'query' || param.in === 'formData')) {
196
+ // Collect params for the FormData body type
197
+ if (!formDataSchema.properties)
198
+ formDataSchema.properties = {};
199
+ formDataSchema.properties[param.name] = param.schema;
200
+ if (param.required) {
201
+ if (!formDataSchema.required)
202
+ formDataSchema.required = [];
203
+ formDataSchema.required.push(param.name);
204
+ }
205
+ }
206
+ else if (param.in === 'path' || param.in === 'query') {
207
+ // Always collect path params
208
+ if (!paramsSchema.properties)
209
+ paramsSchema.properties = {};
210
+ paramsSchema.properties[param.name] = param.schema;
211
+ if (param.required) {
212
+ if (!paramsSchema.required)
213
+ paramsSchema.required = [];
214
+ paramsSchema.required.push(param.name);
215
+ }
216
+ }
217
+ }
218
+ }
219
+ if (isFormData && Object.keys(formDataSchema.properties || {}).length > 0) {
220
+ const typeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Data`;
221
+ requestBodyTypeName = typeName;
222
+ modules[moduleName].schemas[typeName] = formDataSchema;
223
+ referencedTypes.push(typeName);
224
+ jsdocParams.push(...extractJsdocParamsFromSchema(formDataSchema, allSchemas));
225
+ }
226
+ if (Object.keys(paramsSchema.properties || {}).length > 0) {
227
+ requestParamsTypeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Params`;
228
+ modules[moduleName].schemas[requestParamsTypeName] = paramsSchema;
229
+ referencedTypes.push(requestParamsTypeName);
230
+ jsdocParams.push(...extractJsdocParamsFromSchema(paramsSchema, allSchemas));
231
+ }
232
+ }
233
+ // Handle standard Request Body
234
+ if (operation.requestBody && !isReferenceObject(operation.requestBody) && !isFormData) {
235
+ const requestBody = operation.requestBody;
236
+ const jsonContent = (_d = requestBody.content) === null || _d === void 0 ? void 0 : _d['application/json'];
237
+ if (jsonContent === null || jsonContent === void 0 ? void 0 : jsonContent.schema) {
238
+ if (isReferenceObject(jsonContent.schema)) {
239
+ const name = getSchemaName(jsonContent.schema.$ref);
240
+ requestBodyTypeName = name;
241
+ addSchemaWithDependencies(name, modules[moduleName], allSchemas);
242
+ referencedTypes.push(name);
243
+ jsdocParams.push(...extractJsdocParamsFromSchema(jsonContent.schema, allSchemas));
244
+ }
245
+ else {
246
+ // Handle inline schema (including arrays)
247
+ const schema = jsonContent.schema;
248
+ if (schema.type === 'array' || schema.type === 'object') {
249
+ const typeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Data`;
250
+ requestBodyTypeName = typeName;
251
+ modules[moduleName].schemas[typeName] = schema;
252
+ referencedTypes.push(typeName);
253
+ // For arrays, extract jsdoc params from array items
254
+ if (schema.type === 'array' && schema.items) {
255
+ if (isReferenceObject(schema.items)) {
256
+ const itemSchemaName = getSchemaName(schema.items.$ref);
257
+ addSchemaWithDependencies(itemSchemaName, modules[moduleName], allSchemas);
258
+ }
259
+ else {
260
+ jsdocParams.push(...extractJsdocParamsFromSchema(schema.items, allSchemas));
261
+ }
262
+ }
263
+ else if (schema.type === 'object') {
264
+ jsdocParams.push(...extractJsdocParamsFromSchema(schema, allSchemas));
265
+ }
266
+ }
267
+ }
268
+ }
269
+ }
270
+ else if (isFormData && operation.requestBody && !isReferenceObject(operation.requestBody) && !requestBodyTypeName) {
271
+ // Handle cases where FormData is defined in requestBody but we haven't processed it yet
272
+ const formDataContent = (_e = operation.requestBody.content) === null || _e === void 0 ? void 0 : _e['multipart/form-data'];
273
+ if (formDataContent === null || formDataContent === void 0 ? void 0 : formDataContent.schema) {
274
+ const typeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Data`;
275
+ requestBodyTypeName = typeName;
276
+ modules[moduleName].schemas[typeName] = formDataContent.schema;
277
+ referencedTypes.push(typeName);
278
+ jsdocParams.push(...extractJsdocParamsFromSchema(formDataContent.schema, allSchemas));
279
+ }
280
+ }
281
+ // Handle Response Body
282
+ let responseTypeName = 'void';
283
+ const successResponse = operation.responses['200'];
284
+ if (successResponse) {
285
+ const mediaType = ((_f = successResponse.content) === null || _f === void 0 ? void 0 : _f['*/*']) || ((_g = successResponse.content) === null || _g === void 0 ? void 0 : _g['application/json']);
286
+ const schema = mediaType === null || mediaType === void 0 ? void 0 : mediaType.schema;
287
+ if (schema && isReferenceObject(schema)) {
288
+ const name = getSchemaName(schema.$ref);
289
+ responseTypeName = name;
290
+ addSchemaWithDependencies(name, modules[moduleName], allSchemas);
291
+ referencedTypes.push(name);
292
+ }
293
+ }
294
+ // Convert path to template literal string for path parameters
295
+ const urlTemplate = path.replace(/\{(\w+)\}/g, '${params.$1}');
296
+ const endpoint = {
297
+ functionName,
298
+ description: operation.summary || operation.description || '',
299
+ method,
300
+ path: urlTemplate,
301
+ requestParamsTypeName,
302
+ requestBodyTypeName,
303
+ responseTypeName,
304
+ referencedTypes: [...new Set(referencedTypes)],
305
+ hasParams: !!requestParamsTypeName,
306
+ hasData: !!requestBodyTypeName,
307
+ contentType,
308
+ jsdocParams,
309
+ };
310
+ modules[moduleName].endpoints.push(endpoint);
311
+ }
312
+ }
313
+ return modules;
314
+ };
315
+ exports.processApi = processApi;
package/dist/index.js ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ /*
8
+ * @description:
9
+ * @Author: czh
10
+ * @Date: 2025-07-02 10:39:34
11
+ * @LastEditors: Czh
12
+ * @LastEditTime: 2025-07-02 10:39:34
13
+ */
14
+ const commander_1 = require("commander");
15
+ const init_1 = require("./commands/init");
16
+ const build_1 = require("./commands/build");
17
+ const path_1 = __importDefault(require("path"));
18
+ const fs_1 = __importDefault(require("fs"));
19
+ const program = new commander_1.Command();
20
+ // Read package.json to get the version
21
+ const packageJson = JSON.parse(fs_1.default.readFileSync(path_1.default.join(__dirname, '../package.json'), 'utf-8'));
22
+ program
23
+ .name('czh-api')
24
+ .description('A CLI tool to generate API code from Swagger/OpenAPI documents.')
25
+ .version(packageJson.version, '-v, --version', 'output the current version');
26
+ program
27
+ .command('init')
28
+ .description('Initialize czh-api and create a config file and templates.')
29
+ .action(init_1.handleInit);
30
+ program
31
+ .command('build')
32
+ .description('Generate API code based on the configuration file.')
33
+ .action(build_1.handleBuild);
34
+ program.parse(process.argv);
35
+ if (!process.argv.slice(2).length) {
36
+ program.outputHelp();
37
+ }