node-cnb 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/.ide/Dockerfile +20 -0
- package/README.md +57 -0
- package/build.js +148 -0
- package/client.d.ts +662 -0
- package/dist/client.d.ts +970 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +79 -0
- package/dist/index.js.map +1 -0
- package/dist/paths.json +8826 -0
- package/dist/types.d.ts +3642 -0
- package/openapi_swagger.json +17663 -0
- package/package.json +32 -0
- package/pathMethodMap.json +10 -0
- package/src/client.d.ts +970 -0
- package/src/index.d.ts +3 -0
- package/src/index.ts +94 -0
- package/src/paths.json +8826 -0
- package/src/types.d.ts +3642 -0
- package/templates/data_type.tpl +7 -0
- package/tsconfig.json +16 -0
- package/types.d.ts +3678 -0
package/.ide/Dockerfile
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# .ide/Dockerfile
|
|
2
|
+
FROM node:20
|
|
3
|
+
|
|
4
|
+
# 以及按需安装其他软件
|
|
5
|
+
RUN apt-get update && apt-get install -y git
|
|
6
|
+
|
|
7
|
+
# 安装 code-server 和 vscode 常用插件
|
|
8
|
+
RUN curl -fsSL https://code-server.dev/install.sh | sh \
|
|
9
|
+
&& code-server --install-extension redhat.vscode-yaml \
|
|
10
|
+
&& code-server --install-extension dbaeumer.vscode-eslint \
|
|
11
|
+
&& code-server --install-extension eamodio.gitlens \
|
|
12
|
+
&& code-server --install-extension tencent-cloud.coding-copilot \
|
|
13
|
+
&& echo done
|
|
14
|
+
|
|
15
|
+
# 安装 ssh 服务,用于支持 VSCode 客户端通过 Remote-SSH 访问开发环境
|
|
16
|
+
RUN apt-get update && apt-get install -y wget unzip openssh-server
|
|
17
|
+
|
|
18
|
+
# 指定字符集支持命令行输入中文(根据需要选择字符集)
|
|
19
|
+
ENV LANG C.UTF-8
|
|
20
|
+
ENV LANGUAGE C.UTF-8
|
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
node-cnb 是一个用于访问 [CNB API]((https://api.cnb.cool/)) 的 node 编写的 SDK。
|
|
2
|
+
|
|
3
|
+
# 实现原理
|
|
4
|
+
|
|
5
|
+
cnb 会编译出 open api 的 swagger json,并生成 api 页面。
|
|
6
|
+
|
|
7
|
+
该 sdk 依据 swagger json 编译生成对应的 typescript 代码。
|
|
8
|
+
|
|
9
|
+
sdk 内部采用 ES6 新增的语法 Proxy ,在方法调用时按属性路径获取对应的 api 声明,
|
|
10
|
+
组装 api url 和 参数,用 axios 发起 http(s) 请求调用服务端 api。
|
|
11
|
+
|
|
12
|
+
# 安装
|
|
13
|
+
|
|
14
|
+
`npm install node-cnb`
|
|
15
|
+
|
|
16
|
+
# 使用
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { getClient } from "node-cnb";
|
|
20
|
+
|
|
21
|
+
const client = getClient('https://api.cnb.cool', 'xxx');
|
|
22
|
+
|
|
23
|
+
client.users.pinnedRepos.list({
|
|
24
|
+
username: 'xxx'
|
|
25
|
+
}).then((res) => {
|
|
26
|
+
console.log(res);
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
const sdk = require('node-cnb');
|
|
33
|
+
|
|
34
|
+
const client = sdk.getClient('https://api.cnb.cool', 'xxx');
|
|
35
|
+
|
|
36
|
+
client.users.pinnedRepos.list({
|
|
37
|
+
username: 'xxx'
|
|
38
|
+
}).then((res) => {
|
|
39
|
+
console.log(res);
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
# 方法路径
|
|
44
|
+
|
|
45
|
+
方法路径为由如下几个元素组成:
|
|
46
|
+
|
|
47
|
+
1. 第一个 `/-/` 前的参数名
|
|
48
|
+
2. 其他非参数名
|
|
49
|
+
3. http method(若 method 为 get 且返回结果为数组则为 list)
|
|
50
|
+
|
|
51
|
+
例如:
|
|
52
|
+
|
|
53
|
+
`/{repo}/-/git/branches` 的路径下 get 请求的方法名为 `repo.git.branches.list`
|
|
54
|
+
|
|
55
|
+
`'/{repo}/-/git/branches/{branch}'` 的路径下 get 请求方法名为 `repo.git.branches.get`
|
|
56
|
+
|
|
57
|
+
对于此规则下方法路径仍然重复的 api,则单独定义在 [pathMethodMap.json](./pathMethodMap.json)
|
package/build.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const cp = require('child_process');
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const apiSwagger = require('./openapi_swagger.json');
|
|
5
|
+
const { definitions } = apiSwagger;
|
|
6
|
+
const pathMethodNameMap = require('./pathMethodMap.json');
|
|
7
|
+
|
|
8
|
+
const pathMethodsMap = {}
|
|
9
|
+
|
|
10
|
+
const buildPathMethods = () => {
|
|
11
|
+
Object.keys(apiSwagger.paths).forEach(path => {
|
|
12
|
+
const pathValue = apiSwagger.paths[path];
|
|
13
|
+
Object.keys(pathValue).forEach(method => {
|
|
14
|
+
const methodValue = pathValue[method];
|
|
15
|
+
methodValue.path = path;
|
|
16
|
+
methodValue.method = method;
|
|
17
|
+
|
|
18
|
+
const pathMethod = method === 'get' && methodValue.responses['200']?.schema?.type === 'array' ? 'list' : method;
|
|
19
|
+
const methodPath = pathMethodNameMap[path] ? pathMethodNameMap[path] : [...path.replace(/\/\{([^\}]+)\}\/-\//g, '/$1/').replace(/\/\{[^\}]+\}/g, '/').split('/').map(item => _.camelCase(item)).filter(Boolean), pathMethod].join('.');
|
|
20
|
+
const methodValues = pathMethodsMap[methodPath] || []
|
|
21
|
+
methodValues.push(methodValue)
|
|
22
|
+
pathMethodsMap[methodPath] = methodValues;
|
|
23
|
+
if (methodValues.length > 1) {
|
|
24
|
+
console.log('重复的path', methodPath, methodValues.map(item => item.path))
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
fs.writeFileSync('./src/paths.json', JSON.stringify(pathMethodsMap, null, 2));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const convertTypeName = (type) => {
|
|
33
|
+
let typeName = _.camelCase(type.split('.').join('-'));
|
|
34
|
+
typeName = typeName.charAt(0).toUpperCase() + typeName.slice(1);
|
|
35
|
+
return typeName;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const typeMap = {
|
|
39
|
+
'integer': 'number',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function convertSchemaToType(schema) {
|
|
43
|
+
if (schema.type === 'object') {
|
|
44
|
+
let properties = '';
|
|
45
|
+
if (!schema.properties) {
|
|
46
|
+
return 'any'
|
|
47
|
+
}
|
|
48
|
+
for (const [key, value] of Object.entries(schema.properties)) {
|
|
49
|
+
const typeName = key.includes('-')?`'${key}'`: key;
|
|
50
|
+
if(typeName !== key){
|
|
51
|
+
console.log('typeName',key,typeName)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (value.description) {
|
|
55
|
+
properties += `\n /**${value.description}*/\n`
|
|
56
|
+
}
|
|
57
|
+
properties += ` ${typeName}: ${convertSchemaToType(value)};\n`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return `{\n${properties}}`;
|
|
61
|
+
} else if (schema.type === 'array') {
|
|
62
|
+
return `${convertSchemaToType(schema.items)}[]`;
|
|
63
|
+
} else if (schema['$ref']) {
|
|
64
|
+
return convertTypeName(schema['$ref'].replace('#/definitions/', ''))
|
|
65
|
+
} else {
|
|
66
|
+
return typeMap[schema.type] || schema.type || 'any';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const buildDefinitions = () => {
|
|
71
|
+
function convertDefinitionsToTypes(definitions) {
|
|
72
|
+
let types = '';
|
|
73
|
+
|
|
74
|
+
for (const [key, value] of Object.entries(definitions)) {
|
|
75
|
+
|
|
76
|
+
const convetValue = convertSchemaToType(value);
|
|
77
|
+
if (!convetValue) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
types += `export type ${convertTypeName(key)} = ${convetValue};\n\n`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return types;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 提取 definitions 并转换为类型声明
|
|
87
|
+
const types = convertDefinitionsToTypes(definitions);
|
|
88
|
+
|
|
89
|
+
// 将类型声明写入 TypeScript 文件
|
|
90
|
+
fs.writeFileSync('./src/types.d.ts', types, 'utf8');
|
|
91
|
+
}
|
|
92
|
+
const importTypes = [];
|
|
93
|
+
const buildClientInterface = () => {
|
|
94
|
+
const pathTree = {};
|
|
95
|
+
Object.keys(pathMethodsMap).forEach(methodPath => {
|
|
96
|
+
let currentSub = pathTree;
|
|
97
|
+
const methodList = methodPath.split('.');
|
|
98
|
+
methodList.forEach((method) => {
|
|
99
|
+
currentSub[method] = currentSub[method] || {};
|
|
100
|
+
currentSub = currentSub[method];
|
|
101
|
+
})
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const getMethodContent = (tree, path) => {
|
|
105
|
+
let content = '';
|
|
106
|
+
Object.keys(tree).forEach(method => {
|
|
107
|
+
content += `
|
|
108
|
+
${_.camelCase(method)}: ${getMethodContent(tree[method], [path, method].filter(Boolean).join('.'))}
|
|
109
|
+
`;
|
|
110
|
+
})
|
|
111
|
+
if(!content){
|
|
112
|
+
const methodValue = pathMethodsMap[path]?.[0]||{};
|
|
113
|
+
const resultType = convertSchemaToType(methodValue.responses?.['200']?.schema || {});
|
|
114
|
+
importTypes.push(resultType.replace('[]',''))
|
|
115
|
+
|
|
116
|
+
content = `(params: {${(methodValue.parameters||[]).map(param=>{
|
|
117
|
+
|
|
118
|
+
return param.name + (param.required?'':'?') + ': '+convertSchemaToType(param)
|
|
119
|
+
})}}) => Promise<${resultType}>`
|
|
120
|
+
}else {
|
|
121
|
+
content = `{
|
|
122
|
+
${content}
|
|
123
|
+
}`
|
|
124
|
+
}
|
|
125
|
+
return content;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let interfaceContent = `
|
|
129
|
+
export interface Client ${getMethodContent(pathTree, '')}
|
|
130
|
+
`;
|
|
131
|
+
const distinctImportTypes = _.uniq(importTypes.filter(item => !/^[a-z]+$/.test(item))).join(', ');
|
|
132
|
+
interfaceContent = `import {${distinctImportTypes}} from './types';
|
|
133
|
+
${interfaceContent}`
|
|
134
|
+
fs.writeFileSync('./src/client.d.ts', interfaceContent, 'utf8');
|
|
135
|
+
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const prettier = ()=>{
|
|
139
|
+
cp.execSync('npx prettier --write ./src/*.ts');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
buildPathMethods();
|
|
143
|
+
|
|
144
|
+
buildDefinitions();
|
|
145
|
+
|
|
146
|
+
buildClientInterface();
|
|
147
|
+
|
|
148
|
+
prettier();
|