@paulpugovkin/api-docs-axios-ts-generator 1.1.0 → 1.2.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 +237 -495
- package/dist/index.js +78 -0
- package/package.json +11 -22
- package/bin/cli.js +0 -6
- package/src/clean-generated-folder/cleanGeneratedFolder.js +0 -21
- package/src/collect-genereted-files/collectGeneratedFiles.js +0 -33
- package/src/generate-class/generateClass.js +0 -42
- package/src/generate-index-file-with-public-api/generateIndexFileWithPublicApi.js +0 -45
- package/src/generate-interface/generateInterface.js +0 -81
- package/src/generate-js-doc/generateJSDoc.js +0 -136
- package/src/generate-main-index-file/generateMainIndexFile.js +0 -21
- package/src/generate-method/generateMethod.js +0 -264
- package/src/generators/axiosConfigGenerator.js +0 -79
- package/src/index.js +0 -187
- package/src/map-type/mapType.js +0 -19
- package/src/parse-and-generate/parseAndGenerate.js +0 -190
- package/src/resolve-type/resolveType.js +0 -54
- package/src/update-api-docs-json/updateApiDocsJson.js +0 -33
package/dist/index.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var h=(t,o)=>()=>(o||t((o={exports:{}}).exports,o),o.exports);var M=h((at,E)=>{var N=require("fs"),B=require("path");function Ce(t){N.existsSync(t)&&(N.rmSync(t,{recursive:!0}),console.log(`Deleted: ${t}`)),N.mkdirSync(t,{recursive:!0}),N.mkdirSync(B.join(t,"interfaces"),{recursive:!0}),N.mkdirSync(B.join(t,"classes"),{recursive:!0}),console.log("Recreated necessary directories.")}E.exports={cleanGeneratedFolder:Ce}});var _=h((ct,G)=>{var k=require("fs"),Ue=require("path");async function ve(t,o){try{console.log(`Fetching API docs from: ${t}`);let e=await fetch(t);if(!e.ok)throw new Error(`HTTP error! status: ${e.status}`);let s=await e.json();k.mkdirSync(Ue.dirname(o),{recursive:!0}),k.writeFileSync(o,JSON.stringify(s,null,2),{encoding:"utf-8"}),console.log(`Updated and saved API docs to: ${o}`)}catch(e){console.error(`Failed to fetch or save the OpenAPI docs: ${e.message}`)}}G.exports={updateApiDocsJson:ve}});var W=h((pt,J)=>{function Ne(t){return{string:"string",integer:"number",boolean:"boolean",array:"any[]",object:"Record<string, any>",binary:"File"}[t]||"any"}J.exports={mapType:Ne}});var I=h((lt,Q)=>{var{mapType:Te}=W();function R(t,o,e){if(t.$ref){let i=t.$ref.split("/").pop();return e.add(i),i}return t.type==="array"?`Array<${R(t.items||{},o,e)}>`:t.type==="object"?t.properties?"{"+Object.entries(t.properties).map(([s,i])=>`${s}: ${R(i,o,e)}`).join("; ")+"}":"Record<string, any>":t.enum?t.enum.map(s=>`"${s}"`).join(" | "):t.oneOf?t.oneOf.map(i=>R(i,o,e)).join(" | "):t.type?Te(t.type):"any"}Q.exports={resolveType:R}});var H=h((ut,K)=>{var{resolveType:Re}=I(),z=require("path");function Ie(t){return t&&t.charAt(0).toUpperCase()+t.slice(1)}function Fe(t,o,e){let s={},i=[];for(let n of t)if(n.$ref){let a=n.$ref.split("/").pop();e.add(a),i.push(a)}else n.properties&&(s={...s,...n.properties});return{mergedProperties:s,extendsInterfaces:i}}function Le(t,o,e){let s=[];for(let i of t)if(i.$ref){let n=i.$ref.split("/").pop();e.add(n),s.push(n)}return s}function Be(t,o,e,s){let i=new Set,n=o.properties||{},a=[];if(o.allOf){let c=Fe(o.allOf,e,i);n={...n,...c.mergedProperties},a=c.extendsInterfaces}if(o.oneOf){let c=Le(o.oneOf,e,i);a.push(...c)}let m=[];for(let[c,f]of Object.entries(n)){let u=Re(f,e,i);m.push(` ${c}: ${u};`)}let g=s?.outputDir?z.relative(z.join(s.outputDir,"interfaces"),s.outputDir):"./",r=Array.from(i).sort().map(c=>`import type { ${c} } from '${g}';`).join(`
|
|
3
|
+
`),p=a.length?` extends ${a.join(", ")}`:"";return`${r}
|
|
4
|
+
|
|
5
|
+
export interface ${Ie(t)}${p} {
|
|
6
|
+
${m.join(`
|
|
7
|
+
`)}
|
|
8
|
+
}
|
|
9
|
+
`}K.exports={generateInterface:Be}});var X=h((dt,V)=>{var{resolveType:ft}=I();function Ee(t){let o="",e="",s=t.parameters||[],i=t.summary||"",n=["/**"];if(t.requestBody){let r=t.requestBody.content||{};if(r["application/json"]){let p=r["application/json"].schema||{};if(p.oneOf)o=p.oneOf.map(c=>c.$ref.split("/").pop()).join(" | ");else if(p.$ref){let c=p.$ref.split("/").pop();c&&(o=c)}}r["multipart/form-data"]&&(e="FormData")}i&&n.push(` * @description ${i}`),e&&n.push(` * @param {${e}} data - \u0442\u0435\u043B\u043E \u0437\u0430\u043F\u0440\u043E\u0441\u0430`),o&&n.push(` * @param {${o}} values - \u0442\u0435\u043B\u043E \u0437\u0430\u043F\u0440\u043E\u0441\u0430`);let a={},m=[];return s.forEach(r=>{if(r.in==="query")if(r.name.includes(".")){let[p,c]=r.name.split(".");a[p]||(a[p]=[]),a[p].push({propertyName:c,description:r.description||"",type:r.schema.type||"string",required:r.required||!1})}else a[r.name]||(a[r.name]=[]),a[r.name].push({description:r.description||"",type:r.schema.type||"string",required:r.required||!1});else m.push({name:r.name,description:r.description||"",type:r.schema.type||"string",required:r.required||!1})}),Object.keys(a).length>0&&(n.push(" * @param {object} queryParams"),Object.keys(a).forEach(r=>{let c=a[r].sort((f,u)=>(u.required?1:0)-(f.required?1:0));if(c.length===1&&c[0].propertyName===void 0){let f=c[0],u=f.type==="integer"?"number":f.type;n.push(` * @param {${u}} queryParams.${r} - ${f.description}`)}else n.push(` * @param {object} queryParams.${r} - Query parameter`),c.forEach(f=>{let{propertyName:u,description:P,type:A}=f,b=A==="integer"?"number":A;n.push(` * @param {${b}} queryParams.${r}.${u} - ${P}`)})})),m.sort((r,p)=>(p.required?1:0)-(r.required?1:0)).forEach(r=>{let p=r.type==="integer"?"number":r.type;n.push(` * @param {${p}} ${r.name} - ${r.description}`)}),n.push(" * @param {AxiosRequestConfig} config - \u041A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044F axios"),n.push(" */"),n.join(`
|
|
10
|
+
`)}V.exports={generateJSDoc:Ee}});var Z=h((gt,Y)=>{var mt=require("path"),{resolveType:S}=I(),{generateJSDoc:Me}=X();function ke(t,o,e,s,i,n,a={}){let m=a?.options?.allowAxiosConfigSpread!==!1;[...e.matchAll(/\{(\w+)\}/g)].map(l=>l[1]).forEach(l=>{e=e.replace(`{${l}}`,`\${${l}}`)});let r=[],p=[],c=[],f={},u=null,P="",A=!1;for(let l of o.parameters||[]){let d=l.name,$=l.in,q=l.required||!1,O=l.schema||{},Se=O.$ref||O.properties;if($==="path"){let x=S(O,i,n);r.push(`${d}: ${x}`)}else if($==="query")if(Se){let x=S(O,i,n);q?(r.push(`${d}: ${x}`),p.push(`...${d}`)):(c.push(`${d}?: ${x}`),p.push(`...(${d} || {})`))}else if(d.includes(".")){let[x,we]=d.split(".");f[x]=f[x]||[];let De=S(O,i,n);f[x].push(`${we}?: ${De}`)}else{let x=S(O,i,n);q?(p.push(d),r.push(`${d}: ${x}`)):c.push(`${d}?: ${x}`)}}for(let[l,d]of Object.entries(f))c.push(`${l}?: { ${d.join(", ")} }`);let F=["post","put","patch","delete"].includes(s.toLowerCase())&&o.requestBody&&o.requestBody.content&&Object.keys(o.requestBody.content).length>0;if(F){let l=o.requestBody.content||{};if(l["application/json"]){let d=l["application/json"].schema||{};if(d.oneOf)u=`values: ${S(d,i,n)}`;else if(d.$ref){let $=d.$ref.split("/").pop();n.add($),u=`values: ${$}`}else d.properties?u=`values: { ${Object.entries(d.properties).map(([q,O])=>`${q}: ${S(O,i,n)}`).join(", ")} }`:u="values: any";P="values"}else if(l["multipart/form-data"]){A=!0;let d=l["multipart/form-data"].schema||{};if(d.properties)for(let[$,q]of Object.entries(d.properties)){let O=S(q,i,n);q.format==="binary"&&(O="File")}u="data: FormData",P="data"}}let L="void";if(o.responses){for(let[l,d]of Object.entries(o.responses))if(d.content){let $=Object.values(d.content)[0].schema||{};if($.$ref){let q=$.$ref.split("/").pop();L=q,n.add(q)}}}let je=`Promise<AxiosResponse<${L}>>`,C=c.length?"queryParams":null,qe=C?`${C}: { ${c.join(", ")} }`:"",U=p.length>0||c.length>0,Oe=Me(o),Pe=`(${[...r,u,qe,"config?: AxiosRequestConfig"].filter(Boolean).join(", ")}): ${je}`,Ae=`${t}${Pe}`,be=`const fullURL = \`\${BASE_URL}${e}\`;`,y="",v=()=>{let l=[];return p.length>0&&l.push(...p),C&&(l.length>0?l.push(`...${C}`):l.push(`...${C}`)),l.length>0?`{ ${l.join(", ")} }`:"{}"};if(s.toLowerCase()==="delete")u?U?y=`return axios.delete(fullURL, { data: values, ...config, params: ${v()} });`:y="return axios.delete(fullURL, { data: values, ...config });":U?y=`return axios.delete(fullURL, { ...config, params: ${v()} });`:y="return axios.delete(fullURL, config);";else if(s.toLowerCase()==="get")U?y=`return axios.get(fullURL, { ...config, params: ${v()} });`:y="return axios.get(fullURL, config);";else if(F)if(U){let l=v();y=`return axios.${s}(fullURL, ${A?"data":P}, { ...config, params: ${l} });`}else y=`return axios.${s}(fullURL, ${A?"data":P}, config);`;else if(U){let l=v();y=`return axios.${s}(fullURL, null, { ...config, params: ${l} });`}else y=`return axios.${s}(fullURL, null, config);`;return`
|
|
11
|
+
${Oe}
|
|
12
|
+
${Ae} {
|
|
13
|
+
${be}
|
|
14
|
+
${y}
|
|
15
|
+
}
|
|
16
|
+
`}Y.exports={generateMethod:ke}});var oe=h((ht,te)=>{var ee=require("path");function Ge(t,o,e,s){let i=`import type { AxiosResponse, AxiosRequestConfig } from "axios";
|
|
17
|
+
`,n=s?.imports?.axiosPath||"axios",a=s?.imports?.baseUrlPath||"./config/axios/axios",m=s?.outputDir?ee.relative(ee.join(s.outputDir,"classes"),s.outputDir):"../../",g="";e&&e.size>0&&(g=Array.from(e).sort().map(u=>`import { ${u} } from '${m}';`).join(`
|
|
18
|
+
`));let r=`
|
|
19
|
+
import axios from '${n}'`,p=`
|
|
20
|
+
import { BASE_URL } from '${a}'`,c=`${i}${g}${r}${p}`,f=o.join(`
|
|
21
|
+
`);return`${c}
|
|
22
|
+
|
|
23
|
+
export class ${t} {
|
|
24
|
+
${f}
|
|
25
|
+
}
|
|
26
|
+
`}te.exports={generateClass:Ge}});var ae=h((yt,ie)=>{var D=require("fs"),w=require("path"),{generateInterface:_e}=H(),{generateMethod:se}=Z(),{generateClass:re}=oe();function ne(t){D.existsSync(t)||D.mkdirSync(t,{recursive:!0})}function Je(t,o){let e=o?.tags?.prefix||"api_tag_";for(let s of t)if(s.includes(e))return s;return""}function We(t,o){let{include:e,exclude:s,prefix:i}=o?.tags||{};if(e&&e.length>0)return e.includes(t);if(s&&s.length>0)return!s.includes(t);let n=i||"api_tag_";return t.includes(n)}function Qe(t,o){let e={},s=o?.tags?.prefix||"api_tag_";for(let i of t){if(!i.tag)continue;let n=o?.naming?.className?o.naming.className(i.tag):i.tag.replace(s,"");e[n]||(e[n]=[]),e[n].push(i)}return e}async function ze(t,o){let e=JSON.parse(D.readFileSync(t,{encoding:"utf-8"})),s=o.outputDir||w.resolve(process.cwd(),"generated"),i=o.interfacesDir||w.join(s,"interfaces"),n=o.classesDir||w.join(s,"classes");ne(i),ne(n);let a=e?.components?.schemas||{};if(e?.components?.schemas)for(let[r,p]of Object.entries(a)){let c=_e(r,p,a,o);D.writeFileSync(w.join(i,`${r}.ts`),c,{encoding:"utf-8"})}let m=[];if(e?.paths){for(let[r,p]of Object.entries(e.paths))for(let[c,f]of Object.entries(p))if(f?.tags){let u=Je(f.tags,o);if(!u||!We(u,o))continue;m.push({operationId:f.operationId,methodDetails:f,pathKey:r,methodType:c,tag:u})}}if((o.groupBy||"tag")==="all"||o.classMode==="single"){let r=o?.naming?.className?o.naming.className("all"):"ApiClient",p=new Set,c=m.map(u=>se(u.operationId,u.methodDetails,u.pathKey,u.methodType,a,p,o)),f=re(r,c,p,o);D.writeFileSync(w.join(n,`${r}.ts`),f,{encoding:"utf-8"})}else{let r=Qe(m,o);for(let[p,c]of Object.entries(r)){let f=new Set,u=c.map(b=>se(b.operationId,b.methodDetails,b.pathKey,b.methodType,a,f,o)),P=o?.naming?.className?o.naming.className(c[0].tag):p+"Api",A=re(P,u,f,o);D.writeFileSync(w.join(n,`${P}.ts`),A,{encoding:"utf-8"})}}console.log(`Generated ${m.length} API methods.`),console.log(`Generated ${Object.keys(a).length} interfaces.`)}ie.exports={parseAndGenerate:ze}});var ue=h(($t,le)=>{var ce=require("fs"),pe=require("path");function Ke(t,o){let e=[];function s(i){let n=ce.readdirSync(i,{withFileTypes:!0});for(let a of n){let m=pe.join(i,a.name);if(a.isDirectory())s(m);else if(a.isFile()&&a.name.endsWith(".ts")&&a.name!=="index.ts"){let r=pe.relative(t,m).replace(/\\/g,"/").replace(/\.ts$/,"");e.push(`export * from './${r}';`)}}}s(t),ce.writeFileSync(o,e.join(`
|
|
27
|
+
`),{encoding:"utf-8"}),console.log(`Generated ${o} with ${e.length} exports.`)}le.exports={generateIndexFileWithOpenApi:Ke}});var de=h((xt,fe)=>{var He=require("fs"),Ve=require("path");function Xe(t){let o=Ve.join(t,"index.ts"),e=["export * from './interfaces/';","export * from './classes/';"];He.writeFileSync(o,e.join(`
|
|
28
|
+
`),{encoding:"utf-8"}),console.log(`Generated main index.ts at ${o}.`)}fe.exports={generateMainIndexFile:Xe}});var ye=h((jt,he)=>{var me=require("fs"),ge=require("path");function Ye(t,o){let e=ge.join(o,"config","axios");me.mkdirSync(e,{recursive:!0});let s=t.axios||{},i=s.baseURL||"",n=s.timeout||3e4,a=s.headers||{"Content-Type":"application/json"},m=s.withCredentials||!1,g=`import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
|
|
29
|
+
|
|
30
|
+
export const BASE_URL = '${i}';
|
|
31
|
+
|
|
32
|
+
const axiosConfig: AxiosRequestConfig = {
|
|
33
|
+
baseURL: BASE_URL,
|
|
34
|
+
timeout: ${n},
|
|
35
|
+
headers: ${JSON.stringify(a,null,2)},
|
|
36
|
+
withCredentials: ${m},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const axiosInstance: AxiosInstance = axios.create(axiosConfig);
|
|
40
|
+
|
|
41
|
+
// \u041F\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u0447\u0438\u043A \u0437\u0430\u043F\u0440\u043E\u0441\u043E\u0432
|
|
42
|
+
axiosInstance.interceptors.request.use(
|
|
43
|
+
(config) => {
|
|
44
|
+
// \u041C\u043E\u0436\u043D\u043E \u0434\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0442\u043E\u043A\u0435\u043D \u0430\u0432\u0442\u043E\u0440\u0438\u0437\u0430\u0446\u0438\u0438
|
|
45
|
+
// const token = localStorage.getItem('token');
|
|
46
|
+
// if (token) {
|
|
47
|
+
// config.headers.Authorization = \`Bearer \${token}\`;
|
|
48
|
+
// }
|
|
49
|
+
return config;
|
|
50
|
+
},
|
|
51
|
+
(error) => {
|
|
52
|
+
return Promise.reject(error);
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// \u041F\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u0447\u0438\u043A \u043E\u0442\u0432\u0435\u0442\u043E\u0432
|
|
57
|
+
axiosInstance.interceptors.response.use(
|
|
58
|
+
(response) => {
|
|
59
|
+
return response;
|
|
60
|
+
},
|
|
61
|
+
(error) => {
|
|
62
|
+
// \u041E\u0431\u0440\u0430\u0431\u043E\u0442\u043A\u0430 \u043E\u0448\u0438\u0431\u043E\u043A
|
|
63
|
+
if (error.response) {
|
|
64
|
+
// \u0421\u0435\u0440\u0432\u0435\u0440 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0441\u043E \u0441\u0442\u0430\u0442\u0443\u0441\u043E\u043C, \u043E\u0442\u043B\u0438\u0447\u043D\u044B\u043C \u043E\u0442 2xx
|
|
65
|
+
console.error('Response error:', error.response.status, error.response.data);
|
|
66
|
+
} else if (error.request) {
|
|
67
|
+
// \u0417\u0430\u043F\u0440\u043E\u0441 \u0431\u044B\u043B \u0441\u0434\u0435\u043B\u0430\u043D, \u043D\u043E \u043E\u0442\u0432\u0435\u0442 \u043D\u0435 \u043F\u043E\u043B\u0443\u0447\u0435\u043D
|
|
68
|
+
console.error('Request error:', error.request);
|
|
69
|
+
} else {
|
|
70
|
+
// \u041F\u0440\u043E\u0438\u0437\u043E\u0448\u043B\u0430 \u043E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0435 \u0437\u0430\u043F\u0440\u043E\u0441\u0430
|
|
71
|
+
console.error('Error:', error.message);
|
|
72
|
+
}
|
|
73
|
+
return Promise.reject(error);
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
export default axiosInstance;
|
|
78
|
+
`;me.writeFileSync(ge.join(e,"axios.ts"),g,{encoding:"utf-8"}),console.log(`Generated axios config at: ${e}/axios.ts`)}he.exports={generateAxiosConfig:Ye}});var{program:xe}=require("commander"),{cleanGeneratedFolder:Ze}=M(),{updateApiDocsJson:et}=_(),{parseAndGenerate:tt}=ae(),{generateIndexFileWithOpenApi:$e}=ue(),{generateMainIndexFile:ot}=de(),{generateAxiosConfig:st}=ye(),T=require("fs"),j=require("path");async function rt(t,o={}){let e=j.resolve(t);if(!T.existsSync(e))throw new Error(`Configuration file not found: ${e}`);let s=j.extname(e),i;try{if(s===".js"){let a=e;i=require(a)}else if(s===".json"){let a=T.readFileSync(e,"utf8");i=JSON.parse(a)}else if(s===".ts"){let a=require("typescript"),m=T.readFileSync(e,"utf8"),g=a.transpileModule(m,{compilerOptions:{module:1,target:99,esModuleInterop:!0}}),r=e.replace(".ts",".js");T.writeFileSync(r,g.outputText),i=require(r)}else throw new Error("Unsupported configuration file format. Use .js, .json, or .ts")}catch(a){throw new Error(`Failed to load configuration: ${a.message}`)}let n={...i};return Object.keys(o).forEach(a=>{o[a]!==void 0&&(n[a]=o[a])}),n}xe.version("1.0.0").description("Generate TypeScript API client from OpenAPI documentation").option("-c, --config <path>","Path to configuration file").option("--api-docs-url <url>","URL to fetch the OpenAPI documentation").option("--api-docs-path <path>","Local path to OpenAPI documentation file").option("--output-dir <dir>","Output directory for generated files").option("--clean","Clean output directory before generation").parse(process.argv);async function nt(){let t=xe.opts(),o=t.config;if(!o){let r=["api-docs-generator.config.js","api-docs-generator.config.json","api-docs-generator.config.ts"];for(let p of r){let c=j.join(process.cwd(),p);if(T.existsSync(c)){o=c,console.log(`Auto-detected configuration file: ${p}`);break}}}let e=null;if(o)try{e=await rt(o,{apiDocsUrl:t.apiDocsUrl,apiDocsPath:t.apiDocsPath,outputDir:t.outputDir})}catch(r){console.error(`Failed to load configuration: ${r.message}`),process.exit(1)}else{let r=t.apiDocsUrl,p=t.outputDir||j.resolve(process.cwd(),"generated");r||(console.error("Error: API documentation URL is required."),console.error("Please specify --api-docs-url or --api-docs-path, or create a configuration file."),console.error('Run "api-docs-generator --help" for more information.'),process.exit(1)),e={apiDocsUrl:r,outputDir:p,interfacesDir:j.join(p,"interfaces"),classesDir:j.join(p,"classes"),groupBy:"tag",classMode:"multiple",options:{cleanOutputDir:t.clean!==void 0?t.clean:!0,generateAxiosConfig:!1,generateIndexFiles:!0}}}console.log(`Using API Docs URL: ${e.apiDocsUrl}`),console.log(`Output directory: ${e.outputDir||"Not specified - using default"}`);let s=e.outputDir||j.resolve(process.cwd(),"generated"),i=e.interfacesDir||j.join(s,"interfaces"),n=e.classesDir||j.join(s,"classes"),a=j.join(i,"index.ts"),m=j.join(n,"index.ts");e.options?.cleanOutputDir!==!1&&Ze(s),e.apiDocsUrl&&await et(e.apiDocsUrl,"api-docs.json");let g=e.apiDocsPath||"api-docs.json";await tt(g,e),e.options?.generateAxiosConfig&&st(e,s),e.options?.generateIndexFiles!==!1&&(await $e(i,a),await $e(n,m),await ot(s)),console.log("Generation completed successfully.")}nt().catch(t=>console.error("Error during generation:",t));
|
package/package.json
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@paulpugovkin/api-docs-axios-ts-generator",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Generate TypeScript interfaces and axios classes from OpenAPI documentation",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"api-docs-generator": "
|
|
7
|
+
"api-docs-generator": "dist/index.js"
|
|
8
8
|
},
|
|
9
|
-
"types": "src/types/index.d.ts",
|
|
10
9
|
"scripts": {
|
|
11
10
|
"generate-services": "npx @paulpugovkin/api-docs-axios-ts-generator --config api-docs-generator.config.js",
|
|
12
11
|
"dev": "node scripts/dev.js",
|
|
13
12
|
"dev:ts": "ts-node scripts/dev.ts",
|
|
13
|
+
"build": "tsup",
|
|
14
|
+
"prepublishOnly": "npm run build",
|
|
14
15
|
"postinstall": "node src/scripts/createConfigFile.js"
|
|
15
16
|
},
|
|
16
17
|
"keywords": [
|
|
@@ -27,35 +28,23 @@
|
|
|
27
28
|
"author": "",
|
|
28
29
|
"license": "MIT",
|
|
29
30
|
"dependencies": {
|
|
30
|
-
"commander": "^11.0.0"
|
|
31
|
-
|
|
31
|
+
"commander": "^11.0.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
32
34
|
"axios": "^1.6.0"
|
|
33
35
|
},
|
|
34
36
|
"devDependencies": {
|
|
35
37
|
"@types/node": "^20.0.0",
|
|
38
|
+
"tsup": "^8.5.1",
|
|
36
39
|
"typescript": "^5.0.0"
|
|
37
40
|
},
|
|
38
41
|
"engines": {
|
|
39
|
-
"node": ">=
|
|
42
|
+
"node": ">=18.0.0"
|
|
40
43
|
},
|
|
41
44
|
"files": [
|
|
42
|
-
"
|
|
43
|
-
"src/clean-generated-folder",
|
|
44
|
-
"src/collect-genereted-files",
|
|
45
|
-
"src/generate-class",
|
|
46
|
-
"src/generate-index-file-with-public-api",
|
|
47
|
-
"src/generate-interface",
|
|
48
|
-
"src/generate-js-doc",
|
|
49
|
-
"src/generate-main-index-file",
|
|
50
|
-
"src/generate-method",
|
|
51
|
-
"src/generators",
|
|
52
|
-
"src/map-type",
|
|
53
|
-
"src/parse-and-generate",
|
|
54
|
-
"src/resolve-type",
|
|
45
|
+
"dist",
|
|
55
46
|
"src/scripts",
|
|
56
47
|
"src/templates",
|
|
57
|
-
"src/update-api-docs-json",
|
|
58
|
-
"bin",
|
|
59
48
|
"README.md",
|
|
60
49
|
"LICENSE"
|
|
61
50
|
],
|
package/bin/cli.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
const fs = require("fs");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Удаляет содержимое папки и пересоздает её.
|
|
6
|
-
* @param {string} outputDir - Путь к папке.
|
|
7
|
-
*/
|
|
8
|
-
function cleanGeneratedFolder(outputDir) {
|
|
9
|
-
if (fs.existsSync(outputDir)) {
|
|
10
|
-
fs.rmSync(outputDir, {recursive: true}); // Удаляем папку и все вложенные файлы
|
|
11
|
-
console.log(`Deleted: ${outputDir}`);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
fs.mkdirSync(outputDir, {recursive: true}); // Создаем основную папку
|
|
15
|
-
fs.mkdirSync(path.join(outputDir, "interfaces"), {recursive: true});
|
|
16
|
-
fs.mkdirSync(path.join(outputDir, "classes"), {recursive: true});
|
|
17
|
-
|
|
18
|
-
console.log(`Recreated necessary directories.`);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
module.exports = {cleanGeneratedFolder};
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
const fs = require("fs");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
|
|
4
|
-
function collectGeneratedFiles(outputDir) {
|
|
5
|
-
const classFiles = [];
|
|
6
|
-
const interfaceFiles = [];
|
|
7
|
-
|
|
8
|
-
// Рекурсивный обход папок
|
|
9
|
-
function walkDir(currentPath) {
|
|
10
|
-
const entries = fs.readdirSync(currentPath, {withFileTypes: true});
|
|
11
|
-
for (const entry of entries) {
|
|
12
|
-
const fullPath = path.join(currentPath, entry.name);
|
|
13
|
-
|
|
14
|
-
if (entry.isDirectory()) {
|
|
15
|
-
walkDir(fullPath); // Рекурсивный вызов для папок
|
|
16
|
-
} else if (
|
|
17
|
-
entry.isFile() &&
|
|
18
|
-
entry.name.endsWith(".ts") &&
|
|
19
|
-
entry.name !== "index.ts"
|
|
20
|
-
) {
|
|
21
|
-
if (fullPath.includes("interfaces")) {
|
|
22
|
-
interfaceFiles.push(fullPath);
|
|
23
|
-
} else {
|
|
24
|
-
classFiles.push(fullPath);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
walkDir(outputDir); // Запуск обхода с указанной папки
|
|
31
|
-
|
|
32
|
-
return {classFiles, interfaceFiles};
|
|
33
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Генерация классов TypeScript
|
|
3
|
-
* @param {string} name - Название класса
|
|
4
|
-
* @param {string[]} methods - Список методов класса
|
|
5
|
-
* @param {Set<string>} usedInterfaces - Набор используемых интерфейсов
|
|
6
|
-
* @param {Object} config - Конфигурация генератора
|
|
7
|
-
* @returns {string} - Сгенерированный код класса TypeScript
|
|
8
|
-
*/
|
|
9
|
-
const path = require('path');
|
|
10
|
-
|
|
11
|
-
function generateClass(name, methods, usedInterfaces, config) {
|
|
12
|
-
const axiosResponseImport =
|
|
13
|
-
'import { AxiosResponse, AxiosRequestConfig } from "axios";\n';
|
|
14
|
-
|
|
15
|
-
// Определяем пути импорта из конфигурации
|
|
16
|
-
const axiosPath = config?.imports?.axiosPath || 'axios';
|
|
17
|
-
const baseUrlPath = config?.imports?.baseUrlPath || './config/axios/axios';
|
|
18
|
-
|
|
19
|
-
// Определяем базовый путь для импорта интерфейсов (папка выше, чем папка с классами)
|
|
20
|
-
const interfaceImportPath = config?.outputDir
|
|
21
|
-
? path.relative(path.join(config.outputDir, 'classes'), config.outputDir)
|
|
22
|
-
: '../../';
|
|
23
|
-
|
|
24
|
-
// Импорты интерфейсов
|
|
25
|
-
let interfaceImport = '';
|
|
26
|
-
if (usedInterfaces && usedInterfaces.size > 0) {
|
|
27
|
-
interfaceImport = Array.from(usedInterfaces)
|
|
28
|
-
.sort()
|
|
29
|
-
.map((interfaceName) => `import { ${interfaceName} } from '${interfaceImportPath}';`)
|
|
30
|
-
.join("\n");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Импорты axios
|
|
34
|
-
const axiosImport = `\nimport axios from '${axiosPath}'`;
|
|
35
|
-
const baseApiUrl = `\nimport { BASE_URL } from '${baseUrlPath}'`;
|
|
36
|
-
|
|
37
|
-
const imports = `${axiosResponseImport}${interfaceImport}${axiosImport}${baseApiUrl}`;
|
|
38
|
-
const tsMethods = methods.join("\n");
|
|
39
|
-
return `${imports}\n\nexport class ${name} {\n${tsMethods}\n}\n`;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
module.exports = {generateClass};
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
const fs = require("fs");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Генерирует файл `index.ts`, экспортирующий все модули из указанной папки.
|
|
6
|
-
* @param {string} targetDir - Папка, для которой нужно сгенерировать экспорт.
|
|
7
|
-
* @param {string} outputFile - Полный путь для записи файла `index.ts`.
|
|
8
|
-
*/
|
|
9
|
-
function generateIndexFileWithOpenApi(targetDir, outputFile) {
|
|
10
|
-
const exports = [];
|
|
11
|
-
|
|
12
|
-
// Рекурсивно перебираем все файлы в целевой директории
|
|
13
|
-
function walkDir(currentDir) {
|
|
14
|
-
const entries = fs.readdirSync(currentDir, {withFileTypes: true});
|
|
15
|
-
|
|
16
|
-
for (const entry of entries) {
|
|
17
|
-
const fullPath = path.join(currentDir, entry.name);
|
|
18
|
-
|
|
19
|
-
if (entry.isDirectory()) {
|
|
20
|
-
// Рекурсивный вызов для папок
|
|
21
|
-
walkDir(fullPath);
|
|
22
|
-
} else if (
|
|
23
|
-
entry.isFile() &&
|
|
24
|
-
entry.name.endsWith(".ts") &&
|
|
25
|
-
entry.name !== "index.ts"
|
|
26
|
-
) {
|
|
27
|
-
// Вычисляем относительный путь
|
|
28
|
-
const relativePath = path
|
|
29
|
-
.relative(targetDir, fullPath)
|
|
30
|
-
.replace(/\\/g, "/");
|
|
31
|
-
const modulePath = relativePath.replace(/\.ts$/, "");
|
|
32
|
-
exports.push(`export * from './${modulePath}';`);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
walkDir(targetDir); // Запуск обхода с целевой директории
|
|
38
|
-
|
|
39
|
-
// Записываем все экспорты в файл index.ts
|
|
40
|
-
fs.writeFileSync(outputFile, exports.join("\n"), {encoding: "utf-8"});
|
|
41
|
-
|
|
42
|
-
console.log(`Generated ${outputFile} with ${exports.length} exports.`);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
module.exports = {generateIndexFileWithOpenApi};
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
const {resolveType} = require("../resolve-type/resolveType"); // Импорт функции resolveType
|
|
2
|
-
const path = require("path");
|
|
3
|
-
|
|
4
|
-
function capitalizeOnlyFirst(text) {
|
|
5
|
-
return text ? text.charAt(0).toUpperCase() + text.slice(1) : text;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function mergeAllOf(allOf, schemaRefs, usedInterfaces) {
|
|
9
|
-
let mergedProperties = {};
|
|
10
|
-
let extendsInterfaces = [];
|
|
11
|
-
|
|
12
|
-
for (const item of allOf) {
|
|
13
|
-
if (item.$ref) {
|
|
14
|
-
const refName = item.$ref.split("/").pop();
|
|
15
|
-
usedInterfaces.add(refName);
|
|
16
|
-
extendsInterfaces.push(refName);
|
|
17
|
-
} else if (item.properties) {
|
|
18
|
-
mergedProperties = {...mergedProperties, ...item.properties};
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return {mergedProperties, extendsInterfaces};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function processOneOf(oneOf, schemaRefs, usedInterfaces) {
|
|
26
|
-
const unionTypes = [];
|
|
27
|
-
|
|
28
|
-
for (const item of oneOf) {
|
|
29
|
-
if (item.$ref) {
|
|
30
|
-
const refName = item.$ref.split("/").pop();
|
|
31
|
-
usedInterfaces.add(refName);
|
|
32
|
-
unionTypes.push(refName);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return unionTypes;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function generateInterface(name, schema, schemaRefs, config) {
|
|
40
|
-
const usedInterfaces = new Set();
|
|
41
|
-
let properties = schema.properties || {};
|
|
42
|
-
let extendsInterfaces = [];
|
|
43
|
-
|
|
44
|
-
// Обработка allOf
|
|
45
|
-
if (schema.allOf) {
|
|
46
|
-
const merged = mergeAllOf(schema.allOf, schemaRefs, usedInterfaces);
|
|
47
|
-
properties = {...properties, ...merged.mergedProperties};
|
|
48
|
-
extendsInterfaces = merged.extendsInterfaces;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Обработка oneOf
|
|
52
|
-
if (schema.oneOf) {
|
|
53
|
-
const unionTypes = processOneOf(schema.oneOf, schemaRefs, usedInterfaces);
|
|
54
|
-
extendsInterfaces.push(...unionTypes);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const props = [];
|
|
58
|
-
|
|
59
|
-
for (const [propName, propDetails] of Object.entries(properties)) {
|
|
60
|
-
const tsType = resolveType(propDetails, schemaRefs, usedInterfaces);
|
|
61
|
-
props.push(` ${propName}: ${tsType};`);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Определяем базовый путь для импорта интерфейсов
|
|
65
|
-
const interfaceImportPath = config?.outputDir
|
|
66
|
-
? path.relative(path.join(config.outputDir, 'interfaces'), config.outputDir)
|
|
67
|
-
: './';
|
|
68
|
-
|
|
69
|
-
const imports = Array.from(usedInterfaces)
|
|
70
|
-
.sort()
|
|
71
|
-
.map((interfaceName) => `import { ${interfaceName} } from '${interfaceImportPath}';`)
|
|
72
|
-
.join("\n");
|
|
73
|
-
|
|
74
|
-
const extendsClause = extendsInterfaces.length
|
|
75
|
-
? ` extends ${extendsInterfaces.join(", ")}`
|
|
76
|
-
: "";
|
|
77
|
-
|
|
78
|
-
return `${imports}\n\nexport interface ${capitalizeOnlyFirst(name)}${extendsClause} {\n${props.join("\n")}\n}\n`;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
module.exports = {generateInterface};
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
const {resolveType} = require("../resolve-type/resolveType");
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Функция для генерации JSDoc на основе массива объектов
|
|
5
|
-
* @param {object} method - объект метода
|
|
6
|
-
* @returns {string} - JSDoc строка
|
|
7
|
-
*/
|
|
8
|
-
function generateJSDoc(method) {
|
|
9
|
-
|
|
10
|
-
let valueArg = ""
|
|
11
|
-
|
|
12
|
-
let formDataArg = ""
|
|
13
|
-
|
|
14
|
-
const params = method.parameters || [];
|
|
15
|
-
|
|
16
|
-
const summary = method.summary || "";
|
|
17
|
-
|
|
18
|
-
const docLines = [`/**`];
|
|
19
|
-
|
|
20
|
-
if (method.requestBody) {
|
|
21
|
-
const content = method.requestBody.content || {};
|
|
22
|
-
if (content["application/json"]) {
|
|
23
|
-
const schema = content["application/json"].schema || {};
|
|
24
|
-
if (schema.oneOf) {
|
|
25
|
-
valueArg = schema.oneOf.map(s => s.$ref.split("/").pop()).join(' | ');
|
|
26
|
-
} else if (schema.$ref) {
|
|
27
|
-
const refName = schema.$ref.split("/").pop();
|
|
28
|
-
if (!!refName) {
|
|
29
|
-
valueArg = refName
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
if (content["multipart/form-data"]) {
|
|
34
|
-
formDataArg = 'FormData'
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (!!summary) {
|
|
39
|
-
docLines.push(` * @description ${summary}`);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (!!formDataArg) {
|
|
43
|
-
docLines.push(` * @param {${formDataArg}} data - тело запроса`)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (!!valueArg) {
|
|
47
|
-
docLines.push(` * @param {${valueArg}} values - тело запроса`)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const queryParams = {}; // Хранилище для параметров с in: 'query'
|
|
51
|
-
const otherParams = []; // Хранилище для всех остальных параметров
|
|
52
|
-
|
|
53
|
-
// Разделение параметров на query и остальные
|
|
54
|
-
params.forEach((param) => {
|
|
55
|
-
if (param.in === "query") {
|
|
56
|
-
if (param.name.includes(".")) {
|
|
57
|
-
const [objectName, propertyName] = param.name.split(".");
|
|
58
|
-
if (!queryParams[objectName]) {
|
|
59
|
-
queryParams[objectName] = [];
|
|
60
|
-
}
|
|
61
|
-
queryParams[objectName].push({
|
|
62
|
-
propertyName,
|
|
63
|
-
description: param.description || "",
|
|
64
|
-
type: param.schema.type || "string",
|
|
65
|
-
required: param.required || false,
|
|
66
|
-
});
|
|
67
|
-
} else {
|
|
68
|
-
if (!queryParams[param.name]) {
|
|
69
|
-
queryParams[param.name] = [];
|
|
70
|
-
}
|
|
71
|
-
queryParams[param.name].push({
|
|
72
|
-
description: param.description || "",
|
|
73
|
-
type: param.schema.type || "string",
|
|
74
|
-
required: param.required || false,
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
} else {
|
|
78
|
-
otherParams.push({
|
|
79
|
-
name: param.name,
|
|
80
|
-
description: param.description || "",
|
|
81
|
-
type: param.schema.type || "string",
|
|
82
|
-
required: param.required || false,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
// Добавляем queryParams в JSDoc
|
|
88
|
-
if (Object.keys(queryParams).length > 0) {
|
|
89
|
-
docLines.push(` * @param {object} queryParams`);
|
|
90
|
-
Object.keys(queryParams).forEach((key) => {
|
|
91
|
-
const group = queryParams[key];
|
|
92
|
-
// Сортировка: сначала обязательные параметры
|
|
93
|
-
const sortedGroup = group.sort(
|
|
94
|
-
(a, b) => (b.required ? 1 : 0) - (a.required ? 1 : 0)
|
|
95
|
-
);
|
|
96
|
-
if (
|
|
97
|
-
sortedGroup.length === 1 &&
|
|
98
|
-
sortedGroup[0].propertyName === undefined
|
|
99
|
-
) {
|
|
100
|
-
// Одиночный параметр без вложенности
|
|
101
|
-
const param = sortedGroup[0];
|
|
102
|
-
const jsType = param.type === "integer" ? "number" : param.type;
|
|
103
|
-
docLines.push(
|
|
104
|
-
` * @param {${jsType}} queryParams.${key} - ${param.description}`
|
|
105
|
-
);
|
|
106
|
-
} else {
|
|
107
|
-
// Вложенные параметры
|
|
108
|
-
docLines.push(
|
|
109
|
-
` * @param {object} queryParams.${key} - Query parameter`
|
|
110
|
-
);
|
|
111
|
-
sortedGroup.forEach((subParam) => {
|
|
112
|
-
const {propertyName, description, type} = subParam;
|
|
113
|
-
const jsType = type === "integer" ? "number" : type;
|
|
114
|
-
docLines.push(
|
|
115
|
-
` * @param {${jsType}} queryParams.${key}.${propertyName} - ${description}`
|
|
116
|
-
);
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Сортировка других параметров: сначала обязательные
|
|
123
|
-
const sortedOtherParams = otherParams.sort(
|
|
124
|
-
(a, b) => (b.required ? 1 : 0) - (a.required ? 1 : 0)
|
|
125
|
-
);
|
|
126
|
-
sortedOtherParams.forEach((param) => {
|
|
127
|
-
const jsType = param.type === "integer" ? "number" : param.type;
|
|
128
|
-
docLines.push(` * @param {${jsType}} ${param.name} - ${param.description}`);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
docLines.push(` * @param {AxiosRequestConfig} config - Конфигурация axios`);
|
|
132
|
-
docLines.push(` */`);
|
|
133
|
-
return docLines.join("\n");
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
module.exports = {generateJSDoc};
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
const fs = require("fs");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Генерирует главный `index.ts` файл в указанной папке, экспортирующий `openapi.ts` из интерфейсов и классов.
|
|
6
|
-
* @param {string} outputDir - Корневая папка, где будет создан `index.ts`.
|
|
7
|
-
*/
|
|
8
|
-
function generateMainIndexFile(outputDir) {
|
|
9
|
-
const indexFilePath = path.join(outputDir, "index.ts");
|
|
10
|
-
const exports = [
|
|
11
|
-
"export * from './interfaces/';",
|
|
12
|
-
"export * from './classes/';",
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
// Создание или перезапись index.ts
|
|
16
|
-
fs.writeFileSync(indexFilePath, exports.join("\n"), {encoding: "utf-8"});
|
|
17
|
-
|
|
18
|
-
console.log(`Generated main index.ts at ${indexFilePath}.`);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
module.exports = {generateMainIndexFile};
|