@clean-js/api-gen
Version:
[docs](https://lulusir.github.io/clean-js/api-gen/usage) [中文文档](https://github.com/lulusir/clean-js-api-gen/blob/main/README-zh.md)
374 lines (364 loc) • 12.2 kB
JavaScript
;
var jiti = require('jiti');
var path = require('path');
var dayjs = require('dayjs');
var fs = require('fs-extra');
var _ = require('lodash');
var jsonSchemaToTypescript = require('json-schema-to-typescript');
var mkdirp = require('mkdirp');
var tsMorph = require('ts-morph');
class Config {
url = '';
type = 'axios';
outDir = 'clean-js';
diff = true;
zod = false;
mock = false;
loadRuntime() {
const rootDir = process.cwd();
const require = jiti(rootDir, { interopDefault: true, esmResolve: true });
const runtimeConfig = require('./clean.config');
Object.assign(this, runtimeConfig);
}
loadConfig(opt) {
Object.assign(this, opt);
}
getOutPath() {
return path.join(process.cwd(), this.outDir);
}
getServicePath(serviceName = 'http') {
return path.join(this.getOutPath(), `./${serviceName}.service.ts`);
}
getMockPath(serviceName = 'http') {
return path.join(this.getOutPath(), `./${serviceName}.mock.ts`);
}
getAstCachePath() {
return path.join(this.getOutPath(), '.ast.cache.json');
}
getLogPath() {
return path.join(this.getOutPath(), './log');
}
}
const config = new Config();
class Writer {
static async schemaToRenameInterface(schema, name) {
const title = name;
const s = {
...schema,
title,
};
const code = await jsonSchemaToTypescript.compile(s, title, {
bannerComment: '',
additionalProperties: false,
});
return code;
}
static getSourceFile() {
const tplPaths = {
axios: 'axios.tpl.ts',
umi3: 'umi3.tpl.ts',
};
const tplCode = fs.readFileSync(path.join(__dirname, '../template/', tplPaths[config.type || 'axios']), 'utf-8');
const p = new tsMorph.Project({});
const s = p.createSourceFile(config.getServicePath(), tplCode, {
overwrite: true,
});
return s;
}
static cleanOut() {
return fs.rm(config.getOutPath(), { recursive: true }).catch((err) => {
console.log(err);
});
}
static async writeOutFolder() {
if (fs.existsSync(config.getOutPath())) {
await mkdirp(config.getOutPath(), {});
}
}
static async writeFile(filePath, content) {
const fileName = path.basename(filePath);
const folder = path.dirname(filePath);
try {
await mkdirp(folder, {});
fs.writeFileSync(path.join(folder, fileName), content);
}
catch (err) {
console.log(err);
}
}
}
class DiffAnalyzer {
root;
cache = null;
log = {
api: {
data: [],
addLen: 0,
reduceLen: 0,
},
pathParameter: {
data: [],
},
queryParameter: {
data: [],
},
bodyParameter: {
data: [],
},
responseParameter: {
datas: [],
},
};
constructor(root) {
this.root = root;
}
readCache() {
const dir = config.getAstCachePath();
if (fs.existsSync(dir)) {
const data = fs.readJsonSync(dir);
this.cache = data;
}
}
async writeCache() {
const dir = config.getAstCachePath();
await Writer.writeOutFolder();
fs.writeJsonSync(dir, this.root);
}
async visit() {
this.readCache();
await this.writeCache();
if (this.cache === null) {
return;
}
await this.visitRootAST(this.root, this.cache);
}
async visitRootAST(node, cache) {
this.apiDiff(node, cache);
this.pathParamsDiff(node, cache);
this.queryParamsDiff(node, cache);
this.bodyParamsDiff(node, cache);
this.responsesDiff(node, cache);
if (this.hasDiff()) {
console.log('Note that some APIs have changed !!!');
this.writeLog();
}
}
apiDiff(node, cache) {
const cacheApi = cache.requests.map((v) => {
return `${v.method.toUpperCase()} ${v.url}`;
});
const cacheApiMap = cacheApi.reduce((p, c) => {
p[c] = p[c] ? p[c] + 1 : 1;
return p;
}, {});
const nodeApi = node.requests.map((v) => {
return `${v.method.toUpperCase()} ${v.url}`;
});
const nodeApiMap = nodeApi.reduce((p, c) => {
p[c] = p[c] ? p[c] + 1 : 1;
return p;
}, {});
for (let i = 0; i < cacheApi.length; i++) {
const a = cacheApi[i];
if (!nodeApiMap[a]) {
this.log.api.reduceLen += 1;
this.log.api.data.push('Reduce API ' + a);
}
}
for (let i = 0; i < nodeApi.length; i++) {
const a = nodeApi[i];
if (!cacheApiMap[a]) {
this.log.api.addLen += 1;
this.log.api.data.push('Add API ' + a);
}
}
}
pathParamsDiff(node, cache) {
const cacheAPiMap = cache.requests.reduce((p, c) => {
const k = c.method + c.url;
p[k] = c;
return p;
}, {});
for (let i = 0; i < node.requests.length; i++) {
const req = node.requests[i];
const k = req.method + req.url;
const cacheReq = cacheAPiMap[k];
if (cacheReq) {
const isEqual = _.isEqual(cacheReq.pathParams, req.pathParams);
if (!isEqual) {
function clean(p) {
if (p) {
const r = Object.entries(_.cloneDeep(p)).reduce((p, [k, v]) => {
p[k] = v.schema;
return p;
}, {});
return r;
}
return p;
}
this.log.pathParameter.data.push(`Parameters Change in path ${req.method} ${req.url} ${JSON.stringify(clean(cacheReq.pathParams))} -> ${JSON.stringify(clean(req.pathParams))}`);
}
}
}
}
queryParamsDiff(node, cache) {
const cacheAPiMap = cache.requests.reduce((p, c) => {
const k = c.method + c.url;
p[k] = c;
return p;
}, {});
for (let i = 0; i < node.requests.length; i++) {
const req = node.requests[i];
const k = req.method + req.url;
const cacheReq = cacheAPiMap[k];
if (cacheReq) {
const isEqual = _.isEqual(cacheReq.queryParams, req.queryParams);
if (!isEqual) {
function clean(p) {
if (p) {
const r = Object.entries(_.cloneDeep(p)).reduce((p, [k, v]) => {
p[k] = v.schema;
return p;
}, {});
return r;
}
return p;
}
this.log.queryParameter.data.push(`Parameters Change in query ${req.method} ${req.url} ${JSON.stringify(clean(cacheReq.queryParams))} -> ${JSON.stringify(clean(req.queryParams))}`);
}
}
}
}
bodyParamsDiff(node, cache) {
const cacheAPiMap = cache.requests.reduce((p, c) => {
const k = c.method + c.url;
p[k] = c;
return p;
}, {});
for (let i = 0; i < node.requests.length; i++) {
const req = node.requests[i];
const k = req.method + req.url;
const cacheReq = cacheAPiMap[k];
if (cacheReq) {
const isEqual = _.isEqual(cacheReq.bodyParams, req.bodyParams);
if (!isEqual) {
function clean(p) {
if (p) {
const r = Object.entries(_.cloneDeep(p)).reduce((p, [k, v]) => {
p[k] = v.schema;
return p;
}, {});
return r;
}
return p;
}
this.log.bodyParameter.data.push(`Parameters Change in body ${req.method} ${req.url} ${JSON.stringify(clean(cacheReq.bodyParams))} -> ${JSON.stringify(clean(req.bodyParams))}`);
}
}
}
}
responsesDiff(node, cache) {
const cacheAPiMap = cache.requests.reduce((p, c) => {
const k = c.method + c.url;
p[k] = c;
return p;
}, {});
for (let i = 0; i < node.requests.length; i++) {
const req = node.requests[i];
const k = req.method + req.url;
const cacheReq = cacheAPiMap[k];
if (cacheReq) {
const cacheRes200 = cacheReq.responses?.[0];
const nodeRes200 = req.responses?.[0];
const isEqual = _.isEqual(cacheRes200, nodeRes200);
if (!isEqual) {
this.log.responseParameter.datas.push([
`Parameters Change in response ${req.method} ${req.url} \n${JSON.stringify(cacheRes200, null, 2)}\n->\n${JSON.stringify(nodeRes200, null, 2)}`,
]);
}
}
}
}
writeLog() {
const dir = config.getLogPath();
const date = dayjs().format('YYYY-MM-DD_HH-mm-ss');
const fileName = date + '.log';
const apiText = (() => {
let text = '';
if (this.log.api.addLen > 0) {
text += `Added ${this.log.api.addLen} APIs`;
}
if (this.log.api.reduceLen > 0) {
text += ` Reduce ${this.log.api.reduceLen} APIs`;
}
return text;
})();
const apiDiffText = (() => {
if (this.log.api.data.length) {
return this.log.api.data.join('\n');
}
return '';
})();
const modifyText = (() => {
const len = this.log.bodyParameter.data.length +
this.log.pathParameter.data.length +
this.log.queryParameter.data.length +
this.log.responseParameter.datas.length;
if (len > 0) {
return `Modified ${len} APIs `;
}
return '';
})();
const pathText = (() => {
return this.log.pathParameter.data.join('\n') || '';
})();
const queryText = (() => {
return this.log.queryParameter.data.join('\n') || '';
})();
const bodyText = (() => {
return this.log.bodyParameter.data.join('\n') || '';
})();
const responseText = (() => {
return this.log.responseParameter.datas?.[0]?.join('\n') || '';
})();
let text = `
@clean-js/api-gen
Date: ${date}
Sum-up: ${apiText} ${modifyText}
${apiDiffText}
${pathText}
${queryText}
${bodyText}
${responseText}
`;
if (!fs.existsSync(dir)) {
fs.mkdirsSync(dir);
}
fs.writeFileSync(path.join(dir, fileName), text, 'utf-8');
}
hasDiff() {
if (this.log.api.addLen > 0) {
return true;
}
if (this.log.api.reduceLen > 0) {
return true;
}
const len = this.log.bodyParameter.data.length +
this.log.pathParameter.data.length +
this.log.queryParameter.data.length +
this.log.responseParameter.datas.length;
if (len > 0) {
return true;
}
return false;
}
}
process.on('message', async (data) => {
const value = JSON.parse(data);
config.loadConfig(value.config);
console.log('Diffing...');
const diff = new DiffAnalyzer(value.ast);
await diff.visit();
console.log('Diff done...');
process.exit();
});