1862 lines
69 KiB
TypeScript
1862 lines
69 KiB
TypeScript
import assert from 'assert';
|
||
import { join as pathJoin } from 'path';
|
||
import { existsSync, readFileSync, writeFileSync, readdirSync, statSync } from 'fs';
|
||
import * as ts from 'typescript';
|
||
import { firstLetterLowerCase, firstLetterUpperCase } from '../utils/string';
|
||
import { OAK_CLI_MODULE_NAME } from './env';
|
||
import { copySync, mkdirSync } from 'fs-extra';
|
||
import { unset } from 'lodash';
|
||
const { factory } = ts;
|
||
|
||
type DepNode = {
|
||
name: string;
|
||
parent?: DepNode;
|
||
};
|
||
|
||
type DepGraph = {
|
||
nodes: Record<string, 1>;
|
||
dependencies: Record<string, Record<string, 1>>;
|
||
ascOrder: string[];
|
||
};
|
||
|
||
/**
|
||
* 构建项目依赖关系图
|
||
* @param cwd
|
||
* @returns
|
||
*/
|
||
export function analyzeDepedency(cwd: string) {
|
||
const depGraph: DepGraph = {
|
||
nodes: {},
|
||
dependencies: {},
|
||
ascOrder: [],
|
||
};
|
||
|
||
function analyzeOne(dir: string, name: string) {
|
||
if (name) {
|
||
if (!depGraph.nodes[name]) {
|
||
depGraph.nodes[name] = 1;
|
||
depGraph.dependencies[name] = {};
|
||
}
|
||
}
|
||
|
||
let dependencies: string[] = [];
|
||
const depConfigTsFile = join(dir, 'src', 'configuration', 'dependency.ts');
|
||
|
||
if (existsSync(depConfigTsFile)) {
|
||
// 这里依赖配置是ts文件,得翻译成js再读取
|
||
const result = ts.transpileModule(readFileSync(depConfigTsFile, 'utf-8'), { compilerOptions: { module: ts.ModuleKind.CommonJS } });
|
||
dependencies = eval(result.outputText) as string[];
|
||
}
|
||
else {
|
||
const depConfigJsFile = join(dir, 'lib', 'configuration', 'dependency.js');
|
||
if (existsSync(depConfigJsFile)) {
|
||
dependencies = require(depConfigJsFile).default;
|
||
}
|
||
else {
|
||
// 没有依赖文件,直接返回
|
||
return;
|
||
}
|
||
}
|
||
|
||
dependencies.forEach(
|
||
(dep) => {
|
||
if (name) {
|
||
depGraph.dependencies[name][dep] = 1;
|
||
}
|
||
if (!depGraph.nodes[dep]) {
|
||
let dir2 = join(cwd, 'node_modules', dep);
|
||
if (!existsSync(dir2)) {
|
||
dir2 = join(dir, 'node_modules', dep);
|
||
if (!existsSync(dir2)) {
|
||
throw new Error(`找不到依赖包${dep}的安装位置,当前包是${dir}`);
|
||
}
|
||
}
|
||
analyzeOne(dir2, dep);
|
||
}
|
||
}
|
||
);
|
||
}
|
||
|
||
analyzeOne(cwd, '');
|
||
|
||
// 输出一个依赖关系从底向上的序列,类似于图中的反向遍历算法
|
||
do {
|
||
const deps = Object.keys(depGraph.nodes);
|
||
if (deps.length === 0) {
|
||
break;
|
||
}
|
||
const freeNodes: string[] = [];
|
||
for (const n of deps) {
|
||
if (Object.keys(depGraph.dependencies[n]).length === 0) {
|
||
freeNodes.push(n);
|
||
}
|
||
}
|
||
assert(freeNodes.length > 0, '依赖关系成环!');
|
||
freeNodes.forEach(
|
||
(n) => {
|
||
unset(depGraph.nodes, n);
|
||
unset(depGraph.dependencies, n);
|
||
for (const n2 in depGraph.dependencies) {
|
||
unset(depGraph.dependencies[n2], n);
|
||
}
|
||
|
||
depGraph.ascOrder.push(n);
|
||
}
|
||
);
|
||
} while (true);
|
||
|
||
return depGraph;
|
||
}
|
||
|
||
|
||
function join(...paths: string[]) {
|
||
const path = pathJoin(...paths);
|
||
return path.replaceAll('\\', '/');
|
||
}
|
||
|
||
function destructVariableDeclaration(vd: ts.VariableDeclaration) {
|
||
assert(ts.isIdentifier(vd.name))
|
||
assert(vd.name.text.startsWith('total'));
|
||
const on = firstLetterLowerCase(vd.name.text.slice(5));
|
||
const { initializer: nonNullExpression } = vd;
|
||
assert(ts.isNonNullExpression(nonNullExpression!));
|
||
const { expression: callExpression } = nonNullExpression;
|
||
assert(ts.isCallExpression(callExpression));
|
||
assert(ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'mergeConcatMany');
|
||
assert(callExpression.arguments.length === 1);
|
||
const [arg] = callExpression.arguments;
|
||
assert(ts.isAsExpression(arg));
|
||
const { expression } = arg;
|
||
assert(ts.isArrayLiteralExpression(expression));
|
||
return {
|
||
on,
|
||
expression,
|
||
};
|
||
}
|
||
|
||
function outputPolyfillDts(
|
||
dependencies: string[],
|
||
briefNames: string[],
|
||
sourceFile: ts.SourceFile,
|
||
printer: ts.Printer,
|
||
filename: string
|
||
) {
|
||
let statements2: ts.Statement[] = [];
|
||
if (dependencies.length > 0) {
|
||
const { statements } = sourceFile;
|
||
assert(ts.isImportDeclaration(statements[5]) && ts.isModuleDeclaration(statements[6]));
|
||
|
||
const importStatements: ts.Statement[] = [];
|
||
|
||
dependencies.forEach(
|
||
(dep, idx) => {
|
||
importStatements.push(
|
||
factory.createImportDeclaration(
|
||
undefined,
|
||
factory.createImportClause(
|
||
false,
|
||
undefined,
|
||
factory.createNamedImports([factory.createImportSpecifier(
|
||
false,
|
||
factory.createIdentifier("FeatureDict"),
|
||
factory.createIdentifier(`${firstLetterUpperCase(briefNames[idx])}FeatureDict`)
|
||
)])
|
||
),
|
||
factory.createStringLiteral(`${dep}/es/features`),
|
||
undefined
|
||
),
|
||
factory.createImportDeclaration(
|
||
undefined,
|
||
factory.createImportClause(
|
||
false,
|
||
undefined,
|
||
factory.createNamedImports([factory.createImportSpecifier(
|
||
false,
|
||
factory.createIdentifier("AspectDict"),
|
||
factory.createIdentifier(`${firstLetterUpperCase(briefNames[idx])}AspectDict`)
|
||
)])
|
||
),
|
||
factory.createStringLiteral(`${dep}/es/aspects`),
|
||
undefined
|
||
),
|
||
);
|
||
}
|
||
);
|
||
|
||
/**
|
||
* declare global {
|
||
const OakComponent: MakeOakComponent<
|
||
EntityDict,
|
||
BackendRuntimeContext,
|
||
FrontendRuntimeContext,
|
||
AspectDict & OgbAspectDict<EntityDict, BackendRuntimeContext>,
|
||
FeatureDict & OgbFeatureDict<EntityDict>
|
||
>;
|
||
const features: FeatureDict & OgbFeatureDict<EntityDict>;
|
||
}
|
||
*/
|
||
const stmt6 = statements[6] as ts.ModuleDeclaration;
|
||
const { body } = stmt6;
|
||
const [ocStmt, featuresStmt] = (<ts.ModuleBlock>body).statements;
|
||
assert(ts.isVariableStatement(ocStmt) && ts.isVariableStatement(featuresStmt));
|
||
const [ocVd] = ocStmt.declarationList.declarations;
|
||
const [featuresVd] = featuresStmt.declarationList.declarations;
|
||
assert(ts.isVariableDeclaration(ocVd) && ts.isIdentifier(ocVd.name) && ocVd.name.text === 'OakComponent');
|
||
assert(ts.isVariableDeclaration(featuresVd) && ts.isIdentifier(featuresVd.name) && featuresVd.name.text === 'features');
|
||
|
||
const ocType = ocVd.type!;
|
||
assert(ts.isTypeReferenceNode(ocType) && ocType.typeArguments?.length === 5);
|
||
const aspectTypeNode = ocType.typeArguments[3];
|
||
const featureTypeNode = ocType.typeArguments[4];
|
||
assert(ts.isTypeReferenceNode(aspectTypeNode) && ts.isTypeReferenceNode(featureTypeNode));
|
||
Object.assign(ocType, {
|
||
typeArguments: [
|
||
...ocType.typeArguments.slice(0, 3),
|
||
factory.createIntersectionTypeNode(
|
||
[
|
||
aspectTypeNode,
|
||
...briefNames.map(
|
||
(ele) => factory.createTypeReferenceNode(
|
||
factory.createIdentifier(`${firstLetterUpperCase(ele)}AspectDict`),
|
||
[
|
||
factory.createTypeReferenceNode(
|
||
factory.createIdentifier("EntityDict"),
|
||
undefined
|
||
)
|
||
]
|
||
)
|
||
)
|
||
]
|
||
),
|
||
factory.createIntersectionTypeNode(
|
||
[
|
||
featureTypeNode,
|
||
...briefNames.map(
|
||
(ele) => factory.createTypeReferenceNode(
|
||
factory.createIdentifier(`${firstLetterUpperCase(ele)}FeatureDict`),
|
||
[
|
||
factory.createTypeReferenceNode(
|
||
factory.createIdentifier("EntityDict"),
|
||
undefined
|
||
)
|
||
]
|
||
)
|
||
)
|
||
]
|
||
)
|
||
]
|
||
});
|
||
|
||
const featureType = featuresVd.type!;
|
||
assert(ts.isTypeReferenceNode(featureType));
|
||
Object.assign(featuresVd, {
|
||
type: factory.createIntersectionTypeNode(
|
||
[
|
||
featureType,
|
||
...briefNames.map(
|
||
(ele) => factory.createTypeReferenceNode(
|
||
factory.createIdentifier(`${firstLetterUpperCase(ele)}FeatureDict`),
|
||
[
|
||
factory.createTypeReferenceNode(
|
||
factory.createIdentifier("EntityDict"),
|
||
undefined
|
||
)
|
||
]
|
||
)
|
||
)
|
||
]
|
||
)
|
||
})
|
||
|
||
statements2 = [
|
||
...statements.slice(0, 6),
|
||
...importStatements,
|
||
...statements.slice(6)
|
||
];
|
||
}
|
||
else {
|
||
statements2 = [...sourceFile.statements];
|
||
}
|
||
|
||
|
||
const result = printer.printList(
|
||
ts.ListFormat.SourceFileStatements,
|
||
factory.createNodeArray(statements2),
|
||
sourceFile);
|
||
|
||
writeFileSync(filename, result, { flag: 'w' });
|
||
console.log(`构建${filename}文件成功`);
|
||
}
|
||
|
||
function outputDependentExceptions(
|
||
dependencies: string[],
|
||
briefNames: string[],
|
||
sourceFile: ts.SourceFile,
|
||
printer: ts.Printer,
|
||
filename: string
|
||
) {
|
||
let statements2: ts.Statement[] = [];
|
||
if (dependencies.length > 0) {
|
||
const { statements } = sourceFile;
|
||
assert(ts.isImportDeclaration(statements[2]) && ts.isFunctionDeclaration(statements[3]));
|
||
|
||
const importStatements: ts.Statement[] = [];
|
||
|
||
dependencies.forEach(
|
||
(dep, idx) => {
|
||
importStatements.push(
|
||
factory.createImportDeclaration(
|
||
undefined,
|
||
factory.createImportClause(
|
||
false,
|
||
undefined,
|
||
factory.createNamedImports([factory.createImportSpecifier(
|
||
false,
|
||
factory.createIdentifier("makeException"),
|
||
factory.createIdentifier(`make${firstLetterUpperCase(briefNames[idx])}Exception`)
|
||
)])
|
||
),
|
||
factory.createStringLiteral(dep),
|
||
undefined
|
||
)
|
||
);
|
||
}
|
||
);
|
||
|
||
const stmt3 = statements[3] as ts.FunctionDeclaration;
|
||
|
||
const funcStmt0 = stmt3.body?.statements[0]!;
|
||
assert(ts.isVariableStatement(funcStmt0));
|
||
const vd = funcStmt0.declarationList.declarations[0]!;
|
||
const { name, initializer } = vd;
|
||
assert(ts.isIdentifier(name) && name.text === 'e');
|
||
assert(ts.isCallExpression(initializer!));
|
||
|
||
const callExpressions = briefNames.map(
|
||
ele => factory.createCallExpression(
|
||
factory.createIdentifier(`make${firstLetterUpperCase(ele)}Exception`),
|
||
[factory.createTypeReferenceNode(
|
||
factory.createIdentifier("ED"),
|
||
undefined
|
||
)],
|
||
[factory.createIdentifier("data")]
|
||
)
|
||
);
|
||
const rightExpression = callExpressions.length === 1 ? callExpressions[0] :
|
||
callExpressions.length === 2 ? factory.createBinaryExpression(
|
||
callExpressions[0],
|
||
factory.createToken(ts.SyntaxKind.BarBarToken),
|
||
callExpressions[1]
|
||
) : callExpressions.slice(2).reduce(
|
||
(prev, next) => factory.createBinaryExpression(
|
||
prev,
|
||
factory.createToken(ts.SyntaxKind.BarBarToken),
|
||
next,
|
||
), factory.createBinaryExpression(
|
||
callExpressions[0],
|
||
factory.createToken(ts.SyntaxKind.BarBarToken),
|
||
callExpressions[1]
|
||
)
|
||
);
|
||
|
||
Object.assign(vd, {
|
||
initializer: factory.createBinaryExpression(
|
||
initializer!,
|
||
factory.createToken(ts.SyntaxKind.BarBarToken),
|
||
rightExpression,
|
||
)
|
||
});
|
||
statements2 = [
|
||
...statements.slice(0, 3),
|
||
...importStatements,
|
||
...statements.slice(3)
|
||
];
|
||
}
|
||
else {
|
||
statements2 = [...sourceFile.statements];
|
||
}
|
||
|
||
|
||
const result = printer.printList(
|
||
ts.ListFormat.SourceFileStatements,
|
||
factory.createNodeArray(statements2),
|
||
sourceFile);
|
||
|
||
writeFileSync(filename, result, { flag: 'w' });
|
||
console.log(`构建${filename}文件成功`);
|
||
}
|
||
|
||
function outputRuntimeCxt(
|
||
dependencies: string[],
|
||
briefNames: string[],
|
||
sourceFile: ts.SourceFile,
|
||
printer: ts.Printer,
|
||
filename: string
|
||
) {
|
||
let statements2: ts.Statement[] = [];
|
||
if (dependencies.length > 0) {
|
||
const { statements } = sourceFile;
|
||
|
||
const importStatements: ts.Statement[] = [];
|
||
const stmt9 = statements[9];
|
||
const stmt10 = statements[10];
|
||
assert(ts.isTypeAliasDeclaration(stmt9) && ts.isIdentifier(stmt9.name) && stmt9.name.text === 'AAD');
|
||
assert(ts.isTypeAliasDeclaration(stmt10) && ts.isIdentifier(stmt10.name) && stmt10.name.text === 'AFD')
|
||
|
||
assert(ts.isImportDeclaration(statements[5]) && ts.isTypeAliasDeclaration(statements[6]));
|
||
const AADs = [] as string[];
|
||
const AFDs = [] as string[];
|
||
dependencies.forEach(
|
||
(dep, idx) => {
|
||
const featureName = `${firstLetterUpperCase(briefNames[idx])}FeatureDict`;
|
||
const aspectName = `${firstLetterUpperCase(briefNames[idx])}AspectDict`;
|
||
importStatements.push(
|
||
factory.createImportDeclaration(
|
||
undefined,
|
||
factory.createImportClause(
|
||
false,
|
||
undefined,
|
||
factory.createNamedImports([
|
||
factory.createImportSpecifier(
|
||
false,
|
||
factory.createIdentifier("FeatureDict"),
|
||
factory.createIdentifier(featureName)
|
||
),
|
||
factory.createImportSpecifier(
|
||
false,
|
||
factory.createIdentifier("AspectDict"),
|
||
factory.createIdentifier(aspectName)
|
||
)
|
||
])
|
||
),
|
||
factory.createStringLiteral(dep),
|
||
undefined
|
||
)
|
||
);
|
||
AFDs.push(featureName);
|
||
AADs.push(aspectName);
|
||
}
|
||
);
|
||
{
|
||
const { type } = stmt9;
|
||
assert(ts.isTypeReferenceNode(type));
|
||
Object.assign(stmt9, {
|
||
type: factory.createIntersectionTypeNode(
|
||
[
|
||
type,
|
||
...AADs.map(ele => factory.createTypeReferenceNode(factory.createIdentifier(ele), [factory.createTypeReferenceNode('EntityDict')]))
|
||
]
|
||
)
|
||
});
|
||
}
|
||
{
|
||
const { type } = stmt10;
|
||
assert(ts.isIntersectionTypeNode(type));
|
||
const { types } = type;
|
||
Object.assign(type, {
|
||
types: [
|
||
...types,
|
||
...AFDs.map(ele => factory.createTypeReferenceNode(factory.createIdentifier(ele), [factory.createTypeReferenceNode('EntityDict')]))
|
||
]
|
||
});
|
||
}
|
||
|
||
statements2 = [
|
||
...statements.slice(0, 6),
|
||
...importStatements,
|
||
...statements.slice(6)
|
||
];
|
||
}
|
||
else {
|
||
statements2 = [
|
||
...sourceFile.statements
|
||
];
|
||
}
|
||
const result = printer.printList(
|
||
ts.ListFormat.SourceFileStatements,
|
||
factory.createNodeArray(statements2),
|
||
sourceFile);
|
||
|
||
writeFileSync(filename, result, { flag: 'w' });
|
||
console.log(`构建${filename}文件成功`);
|
||
}
|
||
|
||
function outputContext(
|
||
depGraph: DepGraph,
|
||
sourceFile: ts.SourceFile,
|
||
printer: ts.Printer,
|
||
filename: string
|
||
) {
|
||
// 目前只支持单向依赖,未来可以利用mixin来实现多类的继承
|
||
// assert(depGraph.roots.length <= 1);
|
||
let root = depGraph.ascOrder.length > 0 ? depGraph.ascOrder[depGraph.ascOrder.length - 1] : 'oak-frontend-base';
|
||
|
||
const { statements } = sourceFile;
|
||
const isBackend = filename.includes('BackendRuntimeContext');
|
||
|
||
const statements2 = [
|
||
factory.createImportDeclaration(
|
||
undefined,
|
||
factory.createImportClause(
|
||
false,
|
||
factory.createIdentifier(isBackend ? "BaseBackendRuntimeContext" : "BaseFrontendRuntimeContext"),
|
||
undefined
|
||
),
|
||
factory.createStringLiteral(`@${root}/context/${isBackend ? 'BackendRuntimeContext' : 'FrontendRuntimeContext'}`),
|
||
undefined
|
||
),
|
||
...statements
|
||
];
|
||
|
||
const result = printer.printList(
|
||
ts.ListFormat.SourceFileStatements,
|
||
factory.createNodeArray(statements2),
|
||
sourceFile);
|
||
|
||
writeFileSync(filename, result, { flag: 'w' });
|
||
console.log(`构建${filename}文件成功`);
|
||
}
|
||
|
||
/**
|
||
* 生成initialize.prod.ts
|
||
* @param cwd
|
||
* @param dependencies
|
||
* @param briefNames
|
||
* @param sourceFile
|
||
* @param printer
|
||
*/
|
||
function outputIntializeProd(
|
||
cwd: string,
|
||
dependencies: string[],
|
||
briefNames: string[],
|
||
sourceFile: ts.SourceFile,
|
||
printer: ts.Printer,
|
||
filename: string
|
||
) {
|
||
const { statements } = sourceFile;
|
||
|
||
const objectDict: Record<string, string[]> = {};
|
||
// 所有的import
|
||
const importStatements: ts.Statement[] = [];
|
||
dependencies.forEach(
|
||
(dep, idx) => {
|
||
const depDir = join(cwd, 'node_modules', dep);
|
||
if (!existsSync(depDir)) {
|
||
throw new Error(`依赖模块${dep}未能找到相应的安装目录【${depDir}】`);
|
||
}
|
||
const esDir = join(depDir, 'es');
|
||
const libDir = join(depDir, 'lib');
|
||
const esDirExisted = existsSync(esDir);
|
||
const libDirExisted = existsSync(libDir);
|
||
if (!esDirExisted && !libDirExisted) {
|
||
throw new Error(`依赖模块${dep}中没有es或者lib目录`);
|
||
}
|
||
const destDir = esDirExisted ? esDir : libDir;
|
||
const destDirName = esDirExisted ? 'es' : 'lib';
|
||
|
||
const objectDirs = ['checkers'];
|
||
objectDirs.forEach(
|
||
(o) => {
|
||
if (existsSync(join(destDir, o))) {
|
||
const variableName = `${briefNames[idx]}${firstLetterUpperCase(o)}`;
|
||
importStatements.push(
|
||
factory.createImportDeclaration(
|
||
undefined,
|
||
factory.createImportClause(
|
||
false,
|
||
factory.createIdentifier(variableName),
|
||
undefined
|
||
),
|
||
factory.createStringLiteral(join(dep, destDirName, o)),
|
||
undefined
|
||
)
|
||
);
|
||
if (objectDict[o]) {
|
||
objectDict[o].push(variableName);
|
||
}
|
||
else {
|
||
objectDict[o] = [variableName];
|
||
}
|
||
}
|
||
}
|
||
);
|
||
|
||
// common
|
||
if (existsSync(join(destDir, 'configuration'))) {
|
||
const variableName = `${briefNames[idx]}Common`;
|
||
importStatements.push(
|
||
factory.createImportDeclaration(
|
||
undefined,
|
||
factory.createImportClause(
|
||
false,
|
||
factory.createIdentifier(variableName),
|
||
undefined
|
||
),
|
||
factory.createStringLiteral(join(dep, destDirName, 'configuration')),
|
||
undefined
|
||
)
|
||
);
|
||
|
||
if (objectDict.common) {
|
||
objectDict.common.push(variableName);
|
||
}
|
||
else {
|
||
objectDict.common = [variableName];
|
||
}
|
||
}
|
||
|
||
// render
|
||
if (existsSync(join(destDir, 'configuration', 'render.js'))) {
|
||
const variableName = `${briefNames[idx]}Render`;
|
||
importStatements.push(
|
||
factory.createImportDeclaration(
|
||
undefined,
|
||
factory.createImportClause(
|
||
false,
|
||
factory.createIdentifier(variableName),
|
||
undefined
|
||
),
|
||
factory.createStringLiteral(join(dep, destDirName, 'configuration', 'render')),
|
||
undefined
|
||
)
|
||
);
|
||
|
||
if (objectDict.render) {
|
||
objectDict.render.push(variableName);
|
||
}
|
||
else {
|
||
objectDict.render = [variableName];
|
||
}
|
||
}
|
||
|
||
// features
|
||
if (existsSync(join(destDir, 'features'))) {
|
||
importStatements.push(
|
||
factory.createImportDeclaration(
|
||
undefined,
|
||
factory.createImportClause(
|
||
false,
|
||
undefined,
|
||
factory.createNamedImports([
|
||
factory.createImportSpecifier(
|
||
false,
|
||
factory.createIdentifier("create"),
|
||
factory.createIdentifier(`create${firstLetterUpperCase(briefNames[idx])}Features`)
|
||
),
|
||
factory.createImportSpecifier(
|
||
false,
|
||
factory.createIdentifier("FeatureDict"),
|
||
factory.createIdentifier(`${firstLetterUpperCase(briefNames[idx])}FeatureDict`)
|
||
)
|
||
])
|
||
),
|
||
factory.createStringLiteral(join(dep, destDirName, 'features')),
|
||
undefined
|
||
)
|
||
);
|
||
if (objectDict.features) {
|
||
objectDict.features.push(briefNames[idx]);
|
||
}
|
||
else {
|
||
objectDict.features = [briefNames[idx]];
|
||
}
|
||
}
|
||
}
|
||
);
|
||
|
||
|
||
const funcStmt = statements.find(
|
||
(stmt) => ts.isFunctionDeclaration(stmt) && stmt.modifiers?.find(
|
||
modifier => modifier.kind === ts.SyntaxKind.ExportKeyword
|
||
) && stmt.modifiers.find(
|
||
modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword
|
||
)
|
||
) as ts.FunctionDeclaration;
|
||
|
||
assert(funcStmt);
|
||
const idx = statements.indexOf(funcStmt);
|
||
const statements2 = [
|
||
...statements.slice(0, idx),
|
||
...importStatements,
|
||
...statements.slice(idx)
|
||
];
|
||
|
||
const stmt0 = funcStmt.body?.statements[0];
|
||
assert(ts.isVariableStatement(stmt0!));
|
||
let vdl = stmt0.declarationList;
|
||
vdl.declarations.forEach(
|
||
(declaration) => {
|
||
const { on, expression } = destructVariableDeclaration(declaration);
|
||
if (objectDict[on]) {
|
||
const { elements } = expression;
|
||
|
||
Object.assign(expression, {
|
||
elements: elements.concat(...objectDict[on].map(
|
||
ele => factory.createIdentifier(ele)
|
||
))
|
||
});
|
||
}
|
||
}
|
||
);
|
||
|
||
if (objectDict.features) {
|
||
const stmt1 = funcStmt.body?.statements[1];
|
||
assert(ts.isVariableStatement(stmt1!));
|
||
const tfDec = stmt1.declarationList.declarations[0];
|
||
const { name, initializer } = tfDec;
|
||
assert(ts.isIdentifier(name) && name.text === 'totalFeatures');
|
||
assert(ts.isAsExpression(initializer!));
|
||
const { type } = initializer;
|
||
assert(ts.isIntersectionTypeNode(type));
|
||
Object.assign(type, {
|
||
types: type.types.concat(
|
||
objectDict.features.map(
|
||
ele => factory.createTypeReferenceNode(
|
||
`${firstLetterUpperCase(ele)}FeatureDict`,
|
||
[
|
||
factory.createTypeReferenceNode('EntityDict')
|
||
]
|
||
)
|
||
)
|
||
)
|
||
});
|
||
|
||
Object.assign(funcStmt.body!, {
|
||
statements: [
|
||
...funcStmt.body!.statements.slice(0, 4),
|
||
...objectDict.features.map(
|
||
(ele) => [
|
||
factory.createVariableStatement(
|
||
undefined,
|
||
factory.createVariableDeclarationList(
|
||
[factory.createVariableDeclaration(
|
||
factory.createIdentifier(`${ele}Features`),
|
||
undefined,
|
||
undefined,
|
||
factory.createCallExpression(
|
||
factory.createIdentifier(`create${firstLetterUpperCase(ele)}Features`),
|
||
undefined,
|
||
[factory.createIdentifier("totalFeatures")]
|
||
)
|
||
)],
|
||
ts.NodeFlags.Const
|
||
)
|
||
),
|
||
factory.createExpressionStatement(factory.createCallExpression(
|
||
factory.createPropertyAccessExpression(
|
||
factory.createIdentifier("Object"),
|
||
factory.createIdentifier("assign")
|
||
),
|
||
undefined,
|
||
[
|
||
factory.createIdentifier("totalFeatures"),
|
||
factory.createIdentifier(`${ele}Features`)
|
||
]
|
||
))
|
||
]
|
||
).flat(),
|
||
...funcStmt.body!.statements.slice(4),
|
||
]
|
||
})
|
||
}
|
||
|
||
const result = printer.printList(
|
||
ts.ListFormat.SourceFileStatements,
|
||
factory.createNodeArray(statements2),
|
||
sourceFile);
|
||
|
||
writeFileSync(filename, result, { flag: 'w' });
|
||
console.log(`构建${filename}文件成功`);
|
||
}
|
||
|
||
/**
|
||
* 生成initialize.dev.ts
|
||
* @param cwd
|
||
* @param dependencies
|
||
* @param briefNames
|
||
* @param sourceFile
|
||
* @param printer
|
||
*/
|
||
function outputIntializeDev(
|
||
cwd: string,
|
||
dependencies: string[],
|
||
briefNames: string[],
|
||
sourceFile: ts.SourceFile,
|
||
printer: ts.Printer,
|
||
filename: string
|
||
) {
|
||
const { statements } = sourceFile;
|
||
|
||
const objectDict: Record<string, string[]> = {};
|
||
// 所有的import
|
||
const importStatements: ts.Statement[] = [];
|
||
dependencies.forEach(
|
||
(dep, idx) => {
|
||
const depDir = join(cwd, 'node_modules', dep);
|
||
if (!existsSync(depDir)) {
|
||
throw new Error(`依赖模块${dep}未能找到相应的安装目录【${depDir}】`);
|
||
}
|
||
const esDir = join(depDir, 'es');
|
||
const libDir = join(depDir, 'lib');
|
||
const esDirExisted = existsSync(esDir);
|
||
const libDirExisted = existsSync(libDir);
|
||
if (!esDirExisted && !libDirExisted) {
|
||
throw new Error(`依赖模块${dep}中没有es或者lib目录`);
|
||
}
|
||
const destDir = esDirExisted ? esDir : libDir;
|
||
const destDirName = esDirExisted ? 'es' : 'lib';
|
||
|
||
const objectDirs = ['triggers', 'checkers', 'watchers', 'timers', 'data', 'aspects'];
|
||
objectDirs.forEach(
|
||
(o) => {
|
||
if (existsSync(join(destDir, o))) {
|
||
const variableName = `${briefNames[idx]}${firstLetterUpperCase(o)}`;
|
||
importStatements.push(
|
||
factory.createImportDeclaration(
|
||
undefined,
|
||
factory.createImportClause(
|
||
false,
|
||
factory.createIdentifier(variableName),
|
||
undefined
|
||
),
|
||
factory.createStringLiteral(join(dep, destDirName, o)),
|
||
undefined
|
||
)
|
||
);
|
||
if (objectDict[o]) {
|
||
objectDict[o].push(variableName);
|
||
}
|
||
else {
|
||
objectDict[o] = [variableName];
|
||
}
|
||
}
|
||
}
|
||
);
|
||
|
||
// startRoutine
|
||
if (existsSync(join(destDir, 'routines', 'start.js'))) {
|
||
const variableName = `${briefNames[idx]}StartRoutines`;
|
||
importStatements.push(
|
||
factory.createImportDeclaration(
|
||
undefined,
|
||
factory.createImportClause(
|
||
false,
|
||
factory.createIdentifier(variableName),
|
||
undefined
|
||
),
|
||
factory.createStringLiteral(join(dep, destDirName, 'routines/start')),
|
||
undefined
|
||
)
|
||
);
|
||
|
||
if (objectDict.startRoutines) {
|
||
objectDict.startRoutines.push(variableName);
|
||
}
|
||
else {
|
||
objectDict.startRoutines = [variableName];
|
||
}
|
||
}
|
||
|
||
// common
|
||
if (existsSync(join(destDir, 'configuration'))) {
|
||
const variableName = `${briefNames[idx]}Common`;
|
||
importStatements.push(
|
||
factory.createImportDeclaration(
|
||
undefined,
|
||
factory.createImportClause(
|
||
false,
|
||
factory.createIdentifier(variableName),
|
||
undefined
|
||
),
|
||
factory.createStringLiteral(join(dep, destDirName, 'configuration')),
|
||
undefined
|
||
)
|
||
);
|
||
|
||
if (objectDict.common) {
|
||
objectDict.common.push(variableName);
|
||
}
|
||
else {
|
||
objectDict.common = [variableName];
|
||
}
|
||
}
|
||
|
||
// features
|
||
if (existsSync(join(destDir, 'features'))) {
|
||
importStatements.push(
|
||
factory.createImportDeclaration(
|
||
undefined,
|
||
factory.createImportClause(
|
||
false,
|
||
undefined,
|
||
factory.createNamedImports([
|
||
factory.createImportSpecifier(
|
||
false,
|
||
factory.createIdentifier("create"),
|
||
factory.createIdentifier(`create${firstLetterUpperCase(briefNames[idx])}Features`)
|
||
),
|
||
factory.createImportSpecifier(
|
||
false,
|
||
factory.createIdentifier("FeatureDict"),
|
||
factory.createIdentifier(`${firstLetterUpperCase(briefNames[idx])}FeatureDict`)
|
||
)
|
||
])
|
||
),
|
||
factory.createStringLiteral(join(dep, destDirName, 'features')),
|
||
undefined
|
||
)
|
||
);
|
||
if (objectDict.features) {
|
||
objectDict.features.push(briefNames[idx]);
|
||
}
|
||
else {
|
||
objectDict.features = [briefNames[idx]];
|
||
}
|
||
}
|
||
|
||
// ports
|
||
if (existsSync(join(destDir, 'ports'))) {
|
||
const importVariableName = `${briefNames[idx]}Importations`;
|
||
const exportVariableName = `${briefNames[idx]}Exportations`;
|
||
importStatements.push(
|
||
factory.createImportDeclaration(
|
||
undefined,
|
||
factory.createImportClause(
|
||
false,
|
||
undefined,
|
||
factory.createNamedImports([
|
||
factory.createImportSpecifier(
|
||
false,
|
||
factory.createIdentifier("importations"),
|
||
factory.createIdentifier(importVariableName)
|
||
),
|
||
factory.createImportSpecifier(
|
||
false,
|
||
factory.createIdentifier("exportations"),
|
||
factory.createIdentifier(exportVariableName)
|
||
)
|
||
])
|
||
),
|
||
factory.createStringLiteral(join(dep, destDirName, 'ports')),
|
||
undefined
|
||
)
|
||
);
|
||
if (objectDict.importations) {
|
||
objectDict.importations.push(importVariableName);
|
||
}
|
||
else {
|
||
objectDict.importations = [importVariableName];
|
||
}
|
||
|
||
if (objectDict.exportations) {
|
||
objectDict.exportations.push(exportVariableName);
|
||
}
|
||
else {
|
||
objectDict.exportations = [exportVariableName];
|
||
}
|
||
}
|
||
}
|
||
);
|
||
|
||
|
||
const funcStmt = statements.find(
|
||
(stmt) => ts.isFunctionDeclaration(stmt) && stmt.modifiers?.find(
|
||
modifier => modifier.kind === ts.SyntaxKind.ExportKeyword
|
||
) && stmt.modifiers.find(
|
||
modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword
|
||
)
|
||
) as ts.FunctionDeclaration;
|
||
|
||
assert(funcStmt);
|
||
const idx = statements.indexOf(funcStmt);
|
||
const statements2 = [
|
||
...statements.slice(0, idx),
|
||
...importStatements,
|
||
...statements.slice(idx)
|
||
];
|
||
|
||
const stmt0 = funcStmt.body?.statements[0];
|
||
assert(ts.isVariableStatement(stmt0!));
|
||
let vdl = stmt0.declarationList;
|
||
vdl.declarations.forEach(
|
||
(declaration) => {
|
||
const { on, expression } = destructVariableDeclaration(declaration);
|
||
if (objectDict[on]) {
|
||
const { elements } = expression;
|
||
|
||
Object.assign(expression, {
|
||
elements: elements.concat(...objectDict[on].map(
|
||
ele => factory.createIdentifier(ele)
|
||
))
|
||
});
|
||
}
|
||
}
|
||
);
|
||
|
||
if (objectDict.features) {
|
||
const stmt2 = funcStmt.body?.statements[2];
|
||
assert(ts.isVariableStatement(stmt2!));
|
||
const tfDec = stmt2.declarationList.declarations[0];
|
||
const { name, initializer } = tfDec;
|
||
assert(ts.isIdentifier(name) && name.text === 'totalFeatures');
|
||
assert(ts.isAsExpression(initializer!));
|
||
const { type } = initializer;
|
||
assert(ts.isIntersectionTypeNode(type));
|
||
Object.assign(type, {
|
||
types: type.types.concat(
|
||
objectDict.features.map(
|
||
ele => factory.createTypeReferenceNode(
|
||
`${firstLetterUpperCase(ele)}FeatureDict`,
|
||
[
|
||
factory.createTypeReferenceNode('EntityDict')
|
||
]
|
||
)
|
||
)
|
||
)
|
||
});
|
||
|
||
Object.assign(funcStmt.body!, {
|
||
statements: [
|
||
...funcStmt.body!.statements.slice(0, 5),
|
||
...objectDict.features.map(
|
||
(ele) => [
|
||
factory.createVariableStatement(
|
||
undefined,
|
||
factory.createVariableDeclarationList(
|
||
[factory.createVariableDeclaration(
|
||
factory.createIdentifier(`${ele}Features`),
|
||
undefined,
|
||
undefined,
|
||
factory.createCallExpression(
|
||
factory.createIdentifier(`create${firstLetterUpperCase(ele)}Features`),
|
||
undefined,
|
||
[factory.createIdentifier("totalFeatures")]
|
||
)
|
||
)],
|
||
ts.NodeFlags.Const
|
||
)
|
||
),
|
||
factory.createExpressionStatement(factory.createCallExpression(
|
||
factory.createPropertyAccessExpression(
|
||
factory.createIdentifier("Object"),
|
||
factory.createIdentifier("assign")
|
||
),
|
||
undefined,
|
||
[
|
||
factory.createIdentifier("totalFeatures"),
|
||
factory.createIdentifier(`${ele}Features`)
|
||
]
|
||
))
|
||
]
|
||
).flat(),
|
||
...funcStmt.body!.statements.slice(5),
|
||
]
|
||
})
|
||
}
|
||
|
||
const result = printer.printList(
|
||
ts.ListFormat.SourceFileStatements,
|
||
factory.createNodeArray(statements2),
|
||
sourceFile);
|
||
|
||
writeFileSync(filename, result, { flag: 'w' });
|
||
console.log(`构建${filename}文件成功`);
|
||
}
|
||
|
||
/**
|
||
* 根据依赖关系,输出features/index.ts
|
||
* @param cwd
|
||
* @param dependencies
|
||
* @param briefNames
|
||
* @param printer
|
||
* @param filename
|
||
*/
|
||
function outputFeatureIndex(
|
||
dependencies: string[],
|
||
briefNames: string[],
|
||
sourceFile: ts.SourceFile,
|
||
printer: ts.Printer,
|
||
filename: string,
|
||
isModule: boolean,
|
||
) {
|
||
const { statements } = sourceFile;
|
||
let statements2: ts.Statement[] = [];
|
||
if (dependencies.length > 0) {
|
||
const importStatements: ts.Statement[] = [];
|
||
const fdNames: string[] = [];
|
||
const adNames: string[] = [];
|
||
|
||
dependencies.forEach(
|
||
(dep, idx) => {
|
||
const fdName = `${firstLetterUpperCase(briefNames[idx])}FeatureDict`;
|
||
const adName = `${firstLetterUpperCase(briefNames[idx])}AspectDict`;
|
||
// 导入FeatureDict和AspectDict
|
||
importStatements.push(
|
||
factory.createImportDeclaration(
|
||
undefined,
|
||
factory.createImportClause(
|
||
false,
|
||
undefined,
|
||
factory.createNamedImports([
|
||
factory.createImportSpecifier(
|
||
false,
|
||
factory.createIdentifier("FeatureDict"),
|
||
factory.createIdentifier(fdName)
|
||
),
|
||
factory.createImportSpecifier(
|
||
false,
|
||
factory.createIdentifier("AspectDict"),
|
||
factory.createIdentifier(adName)
|
||
)
|
||
])
|
||
),
|
||
factory.createStringLiteral(dep),
|
||
undefined
|
||
),
|
||
);
|
||
fdNames.push(fdName);
|
||
adNames.push(adName);
|
||
}
|
||
);
|
||
|
||
// 导入自己的AspectDict:import { AspectDict } from '../aspects/AspectDict';
|
||
importStatements.push(
|
||
factory.createImportDeclaration(
|
||
undefined,
|
||
factory.createImportClause(
|
||
false,
|
||
undefined,
|
||
factory.createNamedImports([factory.createImportSpecifier(
|
||
false,
|
||
factory.createIdentifier("AspectDict"),
|
||
factory.createIdentifier("ProjectAspectDict")
|
||
)])
|
||
),
|
||
factory.createStringLiteral("../aspects/AspectDict"),
|
||
undefined
|
||
),
|
||
);
|
||
|
||
// 创建一个这样的type: type MergeAspectDict = ProjectAspectDict & GenernalAspectDict<EntityDict>;
|
||
// 除了ProjectAspectDict,还有其他的AspectDict,需要<EntityDict>参数
|
||
const adTypeDeclaration = factory.createTypeAliasDeclaration(
|
||
undefined,
|
||
factory.createIdentifier("MergeAspectDict"),
|
||
undefined,
|
||
factory.createIntersectionTypeNode([
|
||
factory.createTypeReferenceNode(
|
||
factory.createIdentifier("ProjectAspectDict"),
|
||
undefined
|
||
),
|
||
...adNames.map(ad => {
|
||
return factory.createTypeReferenceNode(
|
||
factory.createIdentifier(ad),
|
||
[factory.createTypeReferenceNode(
|
||
factory.createIdentifier("EntityDict"),
|
||
undefined
|
||
)]
|
||
);
|
||
})
|
||
])
|
||
)
|
||
|
||
let i = 0;
|
||
while (true) {
|
||
const stmt = statements[i];
|
||
if (ts.isFunctionDeclaration(stmt)) {
|
||
break;
|
||
}
|
||
i++;
|
||
}
|
||
const stmt3 = statements[i - 1], stmt4 = statements[i];
|
||
assert(ts.isImportDeclaration(stmt3) && ts.isFunctionDeclaration(stmt4));
|
||
const { name, parameters } = stmt4;
|
||
assert(name && ts.isIdentifier(name) && name.text === 'create' && parameters.length === 1);
|
||
const [param] = parameters;
|
||
const { name: paramName, type } = param;
|
||
assert(ts.isIdentifier(paramName) && paramName.text === 'features' && ts.isTypeReferenceNode(type!));
|
||
Object.assign(param, {
|
||
type: factory.createIntersectionTypeNode([
|
||
type,
|
||
...fdNames.map(
|
||
ele => factory.createTypeReferenceNode(ele, [
|
||
factory.createTypeReferenceNode('EntityDict')
|
||
])
|
||
)
|
||
])
|
||
});
|
||
|
||
statements2 = [
|
||
...statements.slice(0, i),
|
||
...importStatements,
|
||
adTypeDeclaration,
|
||
...statements.slice(i)
|
||
];
|
||
if (isModule) {
|
||
statements2.push(
|
||
factory.createFunctionDeclaration(
|
||
[
|
||
factory.createToken(ts.SyntaxKind.ExportKeyword),
|
||
factory.createToken(ts.SyntaxKind.AsyncKeyword)
|
||
],
|
||
undefined,
|
||
factory.createIdentifier("initialize"),
|
||
[
|
||
factory.createTypeParameterDeclaration(
|
||
undefined,
|
||
factory.createIdentifier("ED"),
|
||
factory.createTypeReferenceNode(
|
||
factory.createIdentifier("EntityDict"),
|
||
undefined
|
||
),
|
||
undefined
|
||
)
|
||
],
|
||
[
|
||
factory.createParameterDeclaration(
|
||
undefined,
|
||
undefined,
|
||
factory.createIdentifier("features"),
|
||
undefined,
|
||
factory.createIntersectionTypeNode([
|
||
factory.createTypeReferenceNode(
|
||
factory.createIdentifier("FeatureDict"),
|
||
[factory.createTypeReferenceNode(
|
||
factory.createIdentifier("ED"),
|
||
undefined
|
||
)]
|
||
),
|
||
factory.createTypeReferenceNode(
|
||
factory.createIdentifier("BasicFeatures"),
|
||
[factory.createTypeReferenceNode(
|
||
factory.createIdentifier("ED"),
|
||
undefined
|
||
)]
|
||
),
|
||
...briefNames.map(
|
||
(ele) => factory.createTypeReferenceNode(
|
||
factory.createIdentifier(`${firstLetterUpperCase(ele)}FeatureDict`),
|
||
[factory.createTypeReferenceNode(
|
||
factory.createIdentifier("ED"),
|
||
undefined
|
||
)]
|
||
)
|
||
)
|
||
|
||
]),
|
||
undefined
|
||
)
|
||
],
|
||
undefined,
|
||
factory.createBlock(
|
||
[],
|
||
true
|
||
)
|
||
)
|
||
);
|
||
}
|
||
}
|
||
else {
|
||
statements2 = [...statements];
|
||
}
|
||
|
||
const result = printer.printList(
|
||
ts.ListFormat.SourceFileStatements,
|
||
factory.createNodeArray(statements2),
|
||
sourceFile);
|
||
|
||
writeFileSync(filename, result, { flag: 'w' });
|
||
console.log(`构建${filename}文件成功`);
|
||
}
|
||
|
||
function outputIntializeFeatures(
|
||
cwd: string,
|
||
dependencies: string[],
|
||
briefNames: string[],
|
||
sourceFile: ts.SourceFile,
|
||
printer: ts.Printer,
|
||
filename: string
|
||
) {
|
||
const { statements } = sourceFile;
|
||
|
||
const features: string[] = [];
|
||
// 所有的import
|
||
const importStatements: ts.Statement[] = [];
|
||
// 如果有oak-general-business,需要AccessConfiguration,自动注入
|
||
if (dependencies.includes('oak-general-business')) {
|
||
importStatements.push(
|
||
factory.createImportDeclaration(
|
||
undefined,
|
||
factory.createImportClause(
|
||
false,
|
||
factory.createIdentifier("accessConfiguration"),
|
||
undefined
|
||
),
|
||
factory.createStringLiteral("@project/configuration/access"),
|
||
undefined
|
||
)
|
||
);
|
||
}
|
||
dependencies.forEach(
|
||
(dep, idx) => {
|
||
const depDir = join(cwd, 'node_modules', dep);
|
||
if (!existsSync(depDir)) {
|
||
throw new Error(`依赖模块${dep}未能找到相应的安装目录【${depDir}】`);
|
||
}
|
||
const esDir = join(depDir, 'es');
|
||
const libDir = join(depDir, 'lib');
|
||
const esDirExisted = existsSync(esDir);
|
||
const libDirExisted = existsSync(libDir);
|
||
if (!esDirExisted && !libDirExisted) {
|
||
throw new Error(`依赖模块${dep}中没有es或者lib目录`);
|
||
}
|
||
const destDir = esDirExisted ? esDir : libDir;
|
||
const destDirName = esDirExisted ? 'es' : 'lib';
|
||
|
||
// features
|
||
if (existsSync(join(destDir, 'features'))) {
|
||
importStatements.push(
|
||
factory.createImportDeclaration(
|
||
undefined,
|
||
factory.createImportClause(
|
||
false,
|
||
undefined,
|
||
factory.createNamedImports([
|
||
factory.createImportSpecifier(
|
||
false,
|
||
factory.createIdentifier("initialize"),
|
||
factory.createIdentifier(`initialize${firstLetterUpperCase(briefNames[idx])}Features`)
|
||
),
|
||
factory.createImportSpecifier(
|
||
false,
|
||
factory.createIdentifier("FeatureDict"),
|
||
factory.createIdentifier(`${firstLetterUpperCase(briefNames[idx])}FeatureDict`)
|
||
)
|
||
])
|
||
),
|
||
factory.createStringLiteral(join(dep, destDirName, 'features')),
|
||
undefined
|
||
)
|
||
);
|
||
features.push(briefNames[idx]);
|
||
}
|
||
}
|
||
);
|
||
|
||
const funcStmt = statements.find(
|
||
(stmt) => ts.isFunctionDeclaration(stmt) && stmt.modifiers?.find(
|
||
modifier => modifier.kind === ts.SyntaxKind.ExportKeyword
|
||
) && stmt.modifiers.find(
|
||
modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword
|
||
)
|
||
) as ts.FunctionDeclaration;
|
||
|
||
assert(funcStmt);
|
||
const idx = statements.indexOf(funcStmt);
|
||
const statements2 = [
|
||
...statements.slice(0, idx),
|
||
...importStatements,
|
||
...statements.slice(idx)
|
||
];
|
||
|
||
if (features.length > 0) {
|
||
assert(funcStmt.parameters.length === 1);
|
||
const [param] = funcStmt.parameters;
|
||
const { type } = param!;
|
||
assert(ts.isIntersectionTypeNode(type!));
|
||
Object.assign(type, {
|
||
types: type.types.concat(
|
||
features.map(
|
||
ele => factory.createTypeReferenceNode(
|
||
`${firstLetterUpperCase(ele)}FeatureDict`,
|
||
[
|
||
factory.createTypeReferenceNode('EntityDict')
|
||
]
|
||
)
|
||
)
|
||
)
|
||
});
|
||
}
|
||
|
||
const result = printer.printList(
|
||
ts.ListFormat.SourceFileStatements,
|
||
factory.createNodeArray(statements2),
|
||
sourceFile);
|
||
|
||
writeFileSync(filename, result, { flag: 'w' });
|
||
console.log(`构建${filename}文件成功`);
|
||
}
|
||
|
||
|
||
function injectDataIndexFile(
|
||
dataIndexFile: string,
|
||
briefNames: string[],
|
||
printer: ts.Printer
|
||
) {
|
||
const sourceFile = ts.createSourceFile('index.ts', readFileSync(dataIndexFile, 'utf-8'), ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
||
|
||
const { statements } = sourceFile;
|
||
const importStatements: ts.Statement[] = briefNames.map(
|
||
(ele) => factory.createImportDeclaration(
|
||
undefined,
|
||
factory.createImportClause(
|
||
false,
|
||
factory.createIdentifier(`${ele}Data`),
|
||
undefined
|
||
),
|
||
factory.createStringLiteral(`./${ele}Data`),
|
||
undefined
|
||
)
|
||
);
|
||
|
||
/**
|
||
* 在文件末尾的这个位置上注入引用
|
||
export default {
|
||
relation: relations,
|
||
actionAuth,
|
||
relationAuth,
|
||
path,
|
||
i18n,
|
||
};
|
||
*/
|
||
const exportStmt = statements[statements.length - 1];
|
||
assert(ts.isExportAssignment(exportStmt));
|
||
const { expression } = exportStmt;
|
||
assert(ts.isObjectLiteralExpression(expression));
|
||
const { properties } = expression;
|
||
|
||
Object.assign(expression, {
|
||
properties: [
|
||
...properties,
|
||
...briefNames.map(
|
||
(ele) => factory.createSpreadAssignment(factory.createIdentifier(`${ele}Data`))
|
||
)
|
||
]
|
||
});
|
||
|
||
const statements2 = [
|
||
...importStatements,
|
||
...statements,
|
||
];
|
||
|
||
const result = printer.printList(
|
||
ts.ListFormat.SourceFileStatements,
|
||
factory.createNodeArray(statements2),
|
||
sourceFile);
|
||
|
||
writeFileSync(dataIndexFile, result, { flag: 'w' });
|
||
console.log(`注入${dataIndexFile}文件成功,共注入了${briefNames.length}个初始化数据引用`);
|
||
}
|
||
|
||
/**
|
||
* 将依赖项目的目录去覆盖原来的目录
|
||
* @param fromDir 依赖项目的目录
|
||
* @param toDir 当前项目的目录
|
||
*/
|
||
function tryCopyFilesRecursively(fromDir: string, toDir: string, rebuild?: boolean) {
|
||
const files = readdirSync(fromDir);
|
||
|
||
files.forEach(
|
||
(file) => {
|
||
const fromFile = join(fromDir, file);
|
||
const toFile = join(toDir, file);
|
||
const stat = statSync(fromFile);
|
||
if (stat.isFile()) {
|
||
if (existsSync(join(toDir, file))) {
|
||
if (rebuild) {
|
||
console.log(`覆盖文件${toFile}`);
|
||
}
|
||
else {
|
||
console.log(`忽略文件${toFile}`);
|
||
}
|
||
}
|
||
else {
|
||
console.log(`拷贝文件${toFile}`);
|
||
}
|
||
copySync(fromFile, toFile, {
|
||
overwrite: true,
|
||
});
|
||
}
|
||
else {
|
||
if (!existsSync(toFile)) {
|
||
console.log(`创建文件夹${toFile}`);
|
||
mkdirSync(toFile);
|
||
}
|
||
tryCopyFilesRecursively(fromFile, toFile, rebuild);
|
||
}
|
||
}
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 对各个依赖项目,可能有些文件需要被移植到项目目录下,逐步完善
|
||
* @param cwd
|
||
* @param dependencies
|
||
* @param briefNames
|
||
*/
|
||
function tryCopyModuleTemplateFiles(
|
||
cwd: string,
|
||
dependencies: string[],
|
||
briefNames: string[],
|
||
printer: ts.Printer,
|
||
rebuild?: boolean
|
||
) {
|
||
const injectDataIndexFileDependencies: string[] = [];
|
||
const injectDataIndexFileBriefNames: string[] = [];
|
||
|
||
dependencies.forEach(
|
||
(dep, idx) => {
|
||
const moduleDir = join(cwd, 'node_modules', dep);
|
||
const moduleTemplateDir = join(moduleDir, 'template');
|
||
if (existsSync(moduleTemplateDir)) {
|
||
// data.ts中规定的初始化数据,拷贝到data目录下,并注入到data/index.ts
|
||
const dataFile = join(moduleTemplateDir, 'data.ts');
|
||
if (existsSync(dataFile)) {
|
||
const prjDataFile = join(cwd, 'src', 'data', `${briefNames[idx]}Data.ts`);
|
||
if (!existsSync(prjDataFile) || rebuild) {
|
||
console.log(`拷贝${dataFile}到${prjDataFile}中`);
|
||
copySync(dataFile, prjDataFile);
|
||
injectDataIndexFileDependencies.push(dep);
|
||
injectDataIndexFileBriefNames.push(briefNames[idx]);
|
||
}
|
||
}
|
||
|
||
// src下面的文件是可以拷贝到项目中的
|
||
const srcDir = join(moduleTemplateDir, 'src');
|
||
if (existsSync(srcDir)) {
|
||
tryCopyFilesRecursively(srcDir, join(cwd, 'src'), rebuild);
|
||
}
|
||
}
|
||
}
|
||
);
|
||
|
||
if (injectDataIndexFileBriefNames.length > 0) {
|
||
injectDataIndexFile(join(cwd, 'src', 'data', 'index.ts'), injectDataIndexFileBriefNames, printer);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 对于module类型的项目,在feature/index.ts中注入initialize函数
|
||
* @param cwd
|
||
* @param dependencies
|
||
* @param briefNames
|
||
*/
|
||
function injectInitializeToFeatureIndex(
|
||
cwd: string,
|
||
dependencies: string[],
|
||
briefNames: string[],
|
||
printer: ts.Printer,
|
||
) {
|
||
const featureIndexFile = join(cwd, 'src', 'features', 'index.ts');
|
||
|
||
const sourceFile = ts.createSourceFile('index.ts', readFileSync(featureIndexFile, 'utf-8'), ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
||
const { statements } = sourceFile;
|
||
|
||
const initializeStmt = statements.find(
|
||
(stmt) => ts.isFunctionDeclaration(stmt) && ts.isIdentifier(stmt.name!) && stmt.name.text === 'initialize'
|
||
);
|
||
if (!initializeStmt) {
|
||
const statements2 = [
|
||
...(
|
||
dependencies.map(
|
||
(dep, idx) => factory.createImportDeclaration(
|
||
undefined,
|
||
factory.createImportClause(
|
||
false,
|
||
undefined,
|
||
factory.createNamedImports(
|
||
[factory.createImportSpecifier(
|
||
false,
|
||
factory.createIdentifier("FeatureDict"),
|
||
factory.createIdentifier(`${firstLetterUpperCase(briefNames[idx])}FeatureDict`)
|
||
)]
|
||
)
|
||
),
|
||
factory.createStringLiteral(dep),
|
||
undefined
|
||
)
|
||
|
||
)
|
||
),
|
||
...statements,
|
||
factory.createFunctionDeclaration(
|
||
[
|
||
factory.createToken(ts.SyntaxKind.ExportKeyword),
|
||
factory.createToken(ts.SyntaxKind.AsyncKeyword)
|
||
],
|
||
undefined,
|
||
factory.createIdentifier("initialize"),
|
||
[
|
||
factory.createTypeParameterDeclaration(
|
||
undefined,
|
||
factory.createIdentifier("ED"),
|
||
factory.createTypeReferenceNode(
|
||
factory.createIdentifier("EntityDict"),
|
||
undefined
|
||
),
|
||
undefined
|
||
)
|
||
],
|
||
[
|
||
factory.createParameterDeclaration(
|
||
undefined,
|
||
undefined,
|
||
factory.createIdentifier("features"),
|
||
undefined,
|
||
factory.createIntersectionTypeNode([
|
||
factory.createTypeReferenceNode(
|
||
factory.createIdentifier("FeatureDict"),
|
||
[factory.createTypeReferenceNode(
|
||
factory.createIdentifier("ED"),
|
||
undefined
|
||
)]
|
||
),
|
||
factory.createTypeReferenceNode(
|
||
factory.createIdentifier("BasicFeatures"),
|
||
[factory.createTypeReferenceNode(
|
||
factory.createIdentifier("ED"),
|
||
undefined
|
||
)]
|
||
),
|
||
...briefNames.map(
|
||
(ele) => factory.createTypeReferenceNode(
|
||
factory.createIdentifier(`${firstLetterUpperCase(ele)}FeatureDict`),
|
||
[factory.createTypeReferenceNode(
|
||
factory.createIdentifier("ED"),
|
||
undefined
|
||
)]
|
||
)
|
||
)
|
||
|
||
]),
|
||
undefined
|
||
)
|
||
],
|
||
undefined,
|
||
factory.createBlock(
|
||
[],
|
||
true
|
||
)
|
||
)
|
||
];
|
||
|
||
const result = printer.printList(
|
||
ts.ListFormat.SourceFileStatements,
|
||
factory.createNodeArray(statements2),
|
||
sourceFile);
|
||
|
||
writeFileSync(featureIndexFile, result, { flag: 'w' });
|
||
console.log(`注入${featureIndexFile}文件成功,用户可以自己修正initialize函数的参数和逻辑`);
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* 本函数用于构建src/initialize.dev, src/initialize.prod, src/initializeFeatures, src/context/FrontendContext, src/contextBackendContext
|
||
* 这些和dependency相关的项目文件
|
||
*/
|
||
export default function buildDependency(rebuild?: boolean) {
|
||
const cwd = process.cwd();
|
||
|
||
|
||
const webDir = join(cwd, 'web');
|
||
const isModule = !existsSync(webDir); // 如果没有web目录,说明是module,不需要处理模块级别的文件注入
|
||
|
||
const depConfigFile = join(cwd, 'src', 'configuration', 'dependency.ts');
|
||
if (!existsSync(depConfigFile)) {
|
||
console.error(`${depConfigFile}不存在,无法构建启动文件`);
|
||
}
|
||
|
||
const depGraph = analyzeDepedency(cwd);
|
||
// 依赖如果是树形关系,应当从底层的被依赖者开始初始化
|
||
const dependencies = depGraph.ascOrder;
|
||
|
||
const briefNames = dependencies.map(
|
||
(dep, idx) => `${dep.split('-').map(ele => ele[0]).join('')}${idx}`
|
||
);
|
||
|
||
const templateFileList = [
|
||
join(cwd, 'node_modules', OAK_CLI_MODULE_NAME, 'templateFiles', 'initialize.frontend.ts'),
|
||
join(cwd, 'node_modules', OAK_CLI_MODULE_NAME, 'templateFiles', 'initialize.server.ts'),
|
||
join(cwd, 'node_modules', OAK_CLI_MODULE_NAME, 'templateFiles', 'initializeFeatures.ts'),
|
||
join(cwd, 'node_modules', OAK_CLI_MODULE_NAME, 'templateFiles', 'RuntimeCxt.ts'),
|
||
join(cwd, 'node_modules', OAK_CLI_MODULE_NAME, 'templateFiles', 'DependentExceptions.ts'),
|
||
join(cwd, 'node_modules', OAK_CLI_MODULE_NAME, 'templateFiles', 'polyfill.d.ts'),
|
||
join(cwd, 'node_modules', OAK_CLI_MODULE_NAME, 'templateFiles', 'features/index.ts'),
|
||
join(cwd, 'node_modules', OAK_CLI_MODULE_NAME, 'templateFiles', 'BackendRuntimeContext.ts'),
|
||
join(cwd, 'node_modules', OAK_CLI_MODULE_NAME, 'templateFiles', 'FrontendRuntimeContext.ts')
|
||
];
|
||
|
||
const program = ts.createProgram(templateFileList, {});
|
||
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
||
|
||
let output = true;
|
||
if (!isModule) {
|
||
const initDevFile = join(cwd, 'src', 'initialize.frontend.ts');
|
||
|
||
if (existsSync(initDevFile)) {
|
||
let tips = `[${initDevFile}]文件已经存在`;
|
||
|
||
if (!rebuild) {
|
||
tips += ',忽略构建。';
|
||
output = false;
|
||
}
|
||
else {
|
||
tips += ',将重新构建';
|
||
}
|
||
console.log(tips);
|
||
}
|
||
if (output) {
|
||
outputIntializeDev(cwd, dependencies, briefNames, program.getSourceFile(templateFileList[0])!, printer, initDevFile);
|
||
}
|
||
|
||
output = true;
|
||
const initProdFile = join(cwd, 'src', 'initialize.server.ts');
|
||
if (existsSync(initProdFile) && !rebuild) {
|
||
let tips = `[${initProdFile}]文件已经存在`;
|
||
|
||
if (!rebuild) {
|
||
tips += ',忽略构建。';
|
||
output = false;
|
||
}
|
||
else {
|
||
tips += ',将重新构建';
|
||
}
|
||
console.log(tips);
|
||
}
|
||
if (output) {
|
||
outputIntializeProd(cwd, dependencies, briefNames, program.getSourceFile(templateFileList[1])!, printer, initProdFile);
|
||
}
|
||
|
||
output = true;
|
||
const initFeaturesFile = join(cwd, 'src', 'initializeFeatures.ts');
|
||
if (existsSync(initFeaturesFile) && !rebuild) {
|
||
let tips = `[${initFeaturesFile}]文件已经存在`;
|
||
|
||
if (!rebuild) {
|
||
tips += ',忽略构建。';
|
||
output = false;
|
||
}
|
||
else {
|
||
tips += ',将重新构建,注意如果自定义过初始化过程,需要重新输入';
|
||
}
|
||
console.log(tips);
|
||
}
|
||
if (output) {
|
||
outputIntializeFeatures(cwd, dependencies, briefNames, program.getSourceFile(templateFileList[2])!, printer, initFeaturesFile);
|
||
}
|
||
}
|
||
|
||
output = true;
|
||
const runtimeCxtFile = join(cwd, 'src', 'types', 'RuntimeCxt.ts');
|
||
if (existsSync(runtimeCxtFile) && !rebuild) {
|
||
let tips = `[${runtimeCxtFile}]文件已经存在`;
|
||
|
||
if (!rebuild) {
|
||
tips += ',忽略构建。';
|
||
output = false;
|
||
}
|
||
else {
|
||
tips += ',将重新构建';
|
||
}
|
||
console.log(tips);
|
||
}
|
||
if (output) {
|
||
outputRuntimeCxt(dependencies, briefNames, program.getSourceFile(templateFileList[3])!, printer, runtimeCxtFile);
|
||
}
|
||
|
||
output = true;
|
||
const dependentExceptionsFile = join(cwd, 'src', 'types', 'DependentExceptions.ts');
|
||
if (existsSync(dependentExceptionsFile) && !rebuild) {
|
||
let tips = `[${dependentExceptionsFile}]文件已经存在`;
|
||
|
||
if (!rebuild) {
|
||
tips += ',忽略构建。';
|
||
output = false;
|
||
}
|
||
else {
|
||
tips += ',将重新构建';
|
||
}
|
||
console.log(tips);
|
||
}
|
||
if (output) {
|
||
outputDependentExceptions(dependencies, briefNames, program.getSourceFile(templateFileList[4])!, printer, dependentExceptionsFile);
|
||
}
|
||
|
||
output = true;
|
||
const polyfillDtsFile = join(cwd, 'typings', 'polyfill.d.ts');
|
||
if (existsSync(polyfillDtsFile) && !rebuild) {
|
||
let tips = `[${polyfillDtsFile}]文件已经存在`;
|
||
|
||
if (!rebuild) {
|
||
tips += ',忽略构建。';
|
||
output = false;
|
||
}
|
||
else {
|
||
tips += ',将重新构建';
|
||
}
|
||
console.log(tips);
|
||
}
|
||
if (output) {
|
||
outputPolyfillDts(dependencies, briefNames, program.getSourceFile(templateFileList[5])!, printer, polyfillDtsFile);
|
||
}
|
||
|
||
output = true;
|
||
const featureIndexFile = join(cwd, 'src', 'features', 'index.ts');
|
||
if (existsSync(featureIndexFile) && !rebuild) {
|
||
let tips = `[${featureIndexFile}]文件已经存在`;
|
||
|
||
if (!rebuild) {
|
||
tips += ',忽略构建。';
|
||
output = false;
|
||
}
|
||
else {
|
||
tips += ',将重新构建';
|
||
}
|
||
console.log(tips);
|
||
}
|
||
else {
|
||
outputFeatureIndex(dependencies, briefNames, program.getSourceFile(templateFileList[6])!, printer, featureIndexFile, isModule);
|
||
}
|
||
|
||
output = true;
|
||
let contextFile = join(cwd, 'src', 'context', 'BackendRuntimeContext.ts');
|
||
if (existsSync(contextFile) && !rebuild) {
|
||
let tips = `[${contextFile}]文件已经存在`;
|
||
|
||
if (!rebuild) {
|
||
tips += ',忽略构建。';
|
||
output = false;
|
||
}
|
||
else {
|
||
tips += ',将重新构建,注意如果自定义过上下文的其它方法或属性,需要重新输入';
|
||
}
|
||
console.log(tips);
|
||
}
|
||
if (output) {
|
||
outputContext(depGraph, program.getSourceFile(templateFileList[7])!, printer, contextFile);
|
||
}
|
||
|
||
output = true;
|
||
contextFile = join(cwd, 'src', 'context', 'FrontendRuntimeContext.ts');
|
||
if (existsSync(contextFile) && !rebuild) {
|
||
let tips = `[${contextFile}]文件已经存在`;
|
||
|
||
if (!rebuild) {
|
||
tips += ',忽略构建。';
|
||
output = false;
|
||
}
|
||
else {
|
||
tips += ',将重新构建,注意如果自定义过上下文的其它方法或属性,需要重新输入';
|
||
}
|
||
console.log(tips);
|
||
}
|
||
if (output) {
|
||
outputContext(depGraph, program.getSourceFile(templateFileList[8])!, printer, contextFile);
|
||
}
|
||
|
||
// 把各个依赖项目的一些初始化的文件拷贝过去
|
||
if (!isModule) {
|
||
tryCopyModuleTemplateFiles(cwd, dependencies, briefNames, printer, rebuild);
|
||
}
|
||
} |