/**
* @description get code using standard dataSource format
* @NOTE getd files structure is as below:
* - library (contains class library code)
* - interfaces (contains interfaces code)
* - api.d.ts (contains interfaces and library definitions)
* - api.lock (contains local code state)
*/
import * as _ from 'lodash';
import { StandardDataSource, Interface, Mod, BaseClass } from '../standard';
import { Config } from '../utils';
import * as fs from 'fs-extra';
import * as path from 'path';
import { format } from '../utils';
import { info } from '../debugLog';
import { existsSync } from 'fs-extra';
export class FileStructures {
constructor(private generators: CodeGenerator[], private usingMultipleOrigins: boolean) {}
getMultipleOriginsFileStructures() {
const files = {};
this.generators.forEach(generator => {
const dsName = generator.dataSource.name;
const dsFiles = this.getOriginFileStructures(generator, true);
files[dsName] = dsFiles;
});
return {
...files,
'index.ts': this.getDataSourcesTs.bind(this),
'api.d.ts': this.getDataSourcesDeclarationTs.bind(this),
'api-lock.json': this.getLockContent.bind(this)
};
}
getBaseClassesInDeclaration(originCode: string, usingMultipleOrigins: boolean) {
if (usingMultipleOrigins) {
return `
declare namespace defs {
export ${originCode}
};
`;
}
return `
declare ${originCode}
`;
}
getModsDeclaration(originCode: string, usingMultipleOrigins: boolean) {
if (usingMultipleOrigins) {
return `
declare namespace API {
export ${originCode}
};
`;
}
return `
declare ${originCode}
`;
}
getOriginFileStructures(generator: CodeGenerator, usingMultipleOrigins = false) {
let mods = {};
const dataSource = generator.dataSource;
dataSource.mods.forEach(mod => {
const currMod = {};
mod.interfaces.forEach(inter => {
currMod[inter.name + '.ts'] = generator.getInterfaceContent.bind(generator, inter);
currMod['index.ts'] = generator.getModIndex.bind(generator, mod);
});
mods[mod.name] = currMod;
mods['index.ts'] = generator.getModsIndex.bind(generator);
});
generator.getBaseClassesInDeclaration = this.getBaseClassesInDeclaration.bind(
this,
generator.getBaseClassesInDeclaration(),
usingMultipleOrigins
);
generator.getModsDeclaration = this.getModsDeclaration.bind(
this,
generator.getModsDeclaration(),
usingMultipleOrigins
);
const result = {
'baseClass.ts': generator.getBaseClassesIndex.bind(generator),
mods: mods,
'index.ts': generator.getIndex.bind(generator),
'api.d.ts': generator.getDeclaration.bind(generator)
};
if (!usingMultipleOrigins) {
result['api-lock.json'] = this.getLockContent.bind(this);
}
return result;
}
getFileStructures() {
if (this.usingMultipleOrigins || this.generators.length > 1) {
return this.getMultipleOriginsFileStructures();
} else {
return this.getOriginFileStructures(this.generators[0]);
}
}
getDataSourcesTs() {
const dsNames = this.generators.map(ge => ge.dataSource.name);
return `
${dsNames
.map(name => {
return `import { defs as ${name}Defs, ${name} } from './${name}';
`;
})
.join('\n')}
(window as any).defs = {
${dsNames.map(name => `${name}: ${name}Defs,`).join('\n')}
};
(window as any).API = {
${dsNames.join(',\n')}
};
`;
}
getDataSourcesDeclarationTs() {
const dsNames = this.generators.map(ge => ge.dataSource.name);
return `
${dsNames
.map(name => {
return `/// `;
})
.join('\n')}
`;
}
getLockContent() {
return JSON.stringify(
this.generators.map(ge => ge.dataSource),
null,
2
);
}
}
export class CodeGenerator {
dataSource: StandardDataSource;
constructor() {}
setDataSource(dataSource: StandardDataSource) {
this.dataSource = dataSource;
}
/** 获取某个基类的类型定义代码 */
getBaseClassInDeclaration(base: BaseClass) {
if (base.templateArgs && base.templateArgs.length) {
return `class ${base.name}<${base.templateArgs.map((ignored, index) => `T${index} = any`).join(', ')}> {
${base.properties.map(prop => prop.toPropertyCode(true)).join('\n')}
}
`;
}
return `class ${base.name} {
${base.properties.map(prop => prop.toPropertyCode(true)).join('\n')}
}
`;
}
/** 获取所有基类的类型定义代码,一个 namespace */
getBaseClassesInDeclaration() {
const content = `namespace ${this.dataSource.name || 'defs'} {
${this.dataSource.baseClasses
.map(
base => `
export ${this.getBaseClassInDeclaration(base)}
`
)
.join('\n')}
}
`;
return content;
}
getBaseClassesInDeclarationWithMultipleOrigins() {
return `
declare namespace defs {
export ${this.getBaseClassesInDeclaration()}
}
`;
}
getBaseClassesInDeclarationWithSingleOrigin() {
return `
declare ${this.getBaseClassesInDeclaration()}
`;
}
/** 获取接口内容的类型定义代码 */
getInterfaceContentInDeclaration(inter: Interface) {
const bodyParmas = inter.getBodyParamsCode();
const requestParams = bodyParmas ? `params: Params, bodyParams: ${bodyParmas}` : `params: Params`;
return `
export ${inter.getParamsCode()}
export type Response = ${inter.responseType};
export const init: Response;
export function request(${requestParams}, httpClientConfig?): Promise;
export const url = "${inter.path}";
`;
}
private getInterfaceInDeclaration(inter: Interface) {
return `
/**
* ${inter.description}
* ${inter.path}
*/
export namespace ${inter.name} {
${this.getInterfaceContentInDeclaration(inter)}
}
`;
}
/** 获取模块的类型定义代码,一个 namespace ,一般不需要覆盖 */
getModsDeclaration() {
const mods = this.dataSource.mods;
const content = `namespace ${this.dataSource.name || 'API'} {
${mods
.map(
mod => `
/**
* ${mod.description}
*/
export namespace ${mod.name} {
${mod.interfaces.map(this.getInterfaceInDeclaration.bind(this)).join('\n')}
}
`
)
.join('\n\n')}
}
`;
return content;
}
getModsDeclarationWithMultipleOrigins() {}
getModsDeclarationWithSingleOrigin() {}
/** 获取公共的类型定义代码 */
getCommonDeclaration() {
return '';
}
/** 获取总的类型定义代码 */
getDeclaration() {
return `
type ObjectMap = {
[key in Key]: Value;
}
${this.getCommonDeclaration()}
${this.getBaseClassesInDeclaration()}
${this.getModsDeclaration()}
`;
}
/** 获取接口类和基类的总的 index 入口文件代码 */
getIndex() {
let conclusion = `
import * as defs from './baseClass';
import './mods/';
(window as any).defs = defs;
`;
// dataSource name means multiple dataSource
if (this.dataSource.name) {
conclusion = `
import { ${this.dataSource.name} as defs } from './baseClass';
export { ${this.dataSource.name} } from './mods/';
export { defs };
`;
}
return conclusion;
}
/** 获取所有基类文件代码 */
getBaseClassesIndex() {
const clsCodes = this.dataSource.baseClasses.map(
base => `
class ${base.name} {
${base.properties
.map(prop => {
return prop.toPropertyCodeWithInitValue(base.name);
})
.filter(id => id)
.join('\n')}
}
`
);
if (this.dataSource.name) {
return `
${clsCodes.join('\n')}
export const ${this.dataSource.name} = {
${this.dataSource.baseClasses.map(bs => bs.name).join(',\n')}
}
`;
}
return clsCodes.map(cls => `export ${cls}`).join('\n');
}
/** 获取接口实现内容的代码 */
getInterfaceContent(inter: Interface) {
const bodyParmas = inter.getBodyParamsCode();
const requestParams = bodyParmas ? `params, bodyParams` : `params`;
return `
/**
* @desc ${inter.description}
*/
import * as defs from '../../baseClass';
import pontFetch from 'src/utils/pontFetch';
export ${inter.getParamsCode()}
export const init = ${inter.response.getInitialValue()};
export async function request(${requestParams}, httpClientConfig?) {
return pontFetch({
url: '${inter.path}',
${bodyParmas ? 'params: bodyParams,query:params' : 'params'},
method: '${inter.method}',
httpClientConfig,
});
}
export const url = "${inter.path}";
`;
}
/** 获取单个模块的 index 入口文件 */
getModIndex(mod: Mod) {
return `
/**
* @description ${mod.description}
*/
${mod.interfaces
.map(inter => {
return `import * as ${inter.name} from './${inter.name}';`;
})
.join('\n')}
export {
${mod.interfaces.map(inter => inter.name).join(', \n')}
}
`;
}
/** 获取所有模块的 index 入口文件 */
getModsIndex() {
let conclusion = `
(window as any).API = {
${this.dataSource.mods.map(mod => mod.name).join(', \n')}
};
`;
// dataSource name means multiple dataSource
if (this.dataSource.name) {
conclusion = `
export const ${this.dataSource.name} = {
${this.dataSource.mods.map(mod => mod.name).join(', \n')}
};
`;
}
return `
${this.dataSource.mods
.map(mod => {
return `import * as ${mod.name} from './${mod.name}';`;
})
.join('\n')}
${conclusion}
`;
}
}
export class FilesManager {
// todo: report 可以更改为单例,防止每个地方都注入。
report = info;
prettierConfig: {};
constructor(private fileStructures: FileStructures, private baseDir: string) {}
private setFormat(files: {}) {
_.forEach(files, (value: Function | {}, name: string) => {
if (name.endsWith('.json') || name.endsWith('.lock')) {
return;
}
if (typeof value === 'function') {
files[name] = (content: string) => format(value(content), this.prettierConfig);
}
this.setFormat(value);
});
}
/** 初始化清空路径 */
private initPath(path: string) {
if (fs.existsSync(path)) {
fs.removeSync(path);
}
fs.mkdirpSync(path);
}
async regenerate(report?: typeof info) {
if (report) {
this.report = report;
}
const files = this.fileStructures.getFileStructures();
this.setFormat(files);
this.initPath(this.baseDir);
this.created = true;
await this.generateFiles(files);
}
/** 区分lock文件是创建的还是人为更改的 */
created = false;
async saveLock() {
const lockFilePath = path.join(this.baseDir, 'api-lock.json');
const oldLockFilePath = path.join(this.baseDir, 'api.lock');
const isExists = fs.existsSync(lockFilePath);
const readFilePath = isExists ? lockFilePath : oldLockFilePath;
const lockContent = await fs.readFile(readFilePath, 'utf8');
const newLockContent = this.fileStructures.getLockContent();
if (lockContent !== newLockContent) {
this.created = true;
await fs.writeFile(lockFilePath, newLockContent);
}
}
/** 根据 Codegenerator 配置生成目录和文件 */
async generateFiles(files: {}, dir = this.baseDir) {
const promises = _.map(files, async (value: Function | {}, name) => {
if (typeof value === 'function') {
await fs.writeFile(`${dir}/${name}`, value());
return;
}
await fs.mkdir(`${dir}/${name}`);
// this.report(`文件夹${name}创建成功!`);
await this.generateFiles(value, `${dir}/${name}`);
});
await Promise.all(promises);
}
}