Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { build as buildApp } from './cli/build';
import { generateRequestScriptCli } from './cli/generate-request-script';
import { runProd } from './cli/run-prod';

/** dev 命令 */
const dev = defineCommand({
meta: {
name: 'dev',
Expand All @@ -17,6 +18,7 @@ const dev = defineCommand({
},
});

/** build 命令 */
const build = defineCommand({
meta: {
name: 'build',
Expand All @@ -27,6 +29,7 @@ const build = defineCommand({
},
});

/** run 命令 */
const run = defineCommand({
meta: {
name: 'run',
Expand All @@ -37,6 +40,7 @@ const run = defineCommand({
},
});

/** 生成前端请求文件命令 */
const requestScript = defineCommand({
meta: {
name: 'requestScript',
Expand All @@ -47,6 +51,7 @@ const requestScript = defineCommand({
},
});

/** generate 命令 */
const generate = defineCommand({
meta: {
name: 'generate',
Expand All @@ -55,6 +60,7 @@ const generate = defineCommand({
subCommands: { requestScript },
});

/** tee 命令主入口 */
const main = defineCommand({
meta: {
name: 'tee',
Expand Down
17 changes: 17 additions & 0 deletions src/cli/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { resolve } from 'pathe';
import { getStorage, setStorage } from '../storage';
import { consola, getPkgInfo, loadModule, parseConfig, parseOptions, runSourceMain } from '../utils';

/**
* 初始化应用, 返回应用实例和路由
*/
export async function bootstrap(_options?: GenerateTypeOptions) {
const options = await parseOptions(_options);

Expand Down Expand Up @@ -45,6 +48,9 @@ function errorHandler(e: Error) {
consola.error(e);
}

/**
* 重启服务
*/
async function restart(options: DevOptions) {
try {
const oldServer = getStorage('server');
Expand All @@ -60,12 +66,20 @@ async function restart(options: DevOptions) {
}
}

/**
* 监听文件变化的处理函数
*
* 打印日志和重启服务
*/
const watchHandler = debounce(async (options: DevOptions) => {
consola.start('Watching for changes...');
await restart(options);
consola.success('Restarted');
});

/**
* 监听文件变化
*/
async function devHandler(options: DevOptions) {
const { pkgPath, sourceDir } = options;
fs.watchFile(resolve(pkgPath, 'main.ts'), () => watchHandler(options));
Expand All @@ -76,6 +90,9 @@ async function devHandler(options: DevOptions) {
});
}

/**
* 命令行初始化应用的函数, 进行一些前后置处理
*/
export async function bootstrapCli() {
const { port, sourceDir } = await parseConfig();
const { pkgPath } = await getPkgInfo();
Expand Down
18 changes: 16 additions & 2 deletions src/cli/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ interface BuildOptions extends BuildConfig {
sourceDir?: string;
}

/**
* 解析构建参数
*/
async function parseOptions(options?: BuildOptions) {
const { sourceDir, build: { outDir, ...rest } } = await parseConfig();

Expand All @@ -19,6 +22,9 @@ async function parseOptions(options?: BuildOptions) {
});
}

/**
* 格式化入口文件信息
*/
function entryFormat(fileInfoMap: FileInfoMap): { in: string; out: string }[] {
const entrys: { in: string; out: string }[] = [];
for (const type in fileInfoMap) {
Expand All @@ -30,7 +36,12 @@ function entryFormat(fileInfoMap: FileInfoMap): { in: string; out: string }[] {
return entrys;
}

function getFilePoints(filePath: string) {
/**
* 获取单个文件的 point 信息
*
* 使用数组作为返回值, 是为了防止文件不存在返回 undefined 导致 esbuild 报错
*/
function getFilePoint(filePath: string) {
const filePoints: { in: string; out: string }[] = [];
if (existsSync(filePath)) {
const out = basename(filePath, '.ts');
Expand All @@ -39,6 +50,9 @@ function getFilePoints(filePath: string) {
return filePoints;
}

/**
* 打包代码
*/
export async function build(_options?: BuildOptions) {
const options = await parseOptions(_options);
const { outDir: outdir, clean } = options;
Expand All @@ -55,7 +69,7 @@ export async function build(_options?: BuildOptions) {
await esbuild({
entryPoints: [
...entryPoints,
...getFilePoints(mainFilePath),
...getFilePoint(mainFilePath),
],
outdir,
minify: true,
Expand Down
46 changes: 46 additions & 0 deletions src/cli/generate-request-script/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ import { getStorage, setStorage } from '../../storage';
import { getPkgInfo, loadModule, parseConfig, parseOptions } from '../../utils';
import { API_METHOD_SYMBOL, API_TYPE_SYMBOL, template } from './template';

/**
* 过滤无效的路由
*
* TODO: 暂时没有无效的定义, 留个口子
*/
function filterValidRouter(routerInfoMap: Record<string, RouterInfo>) {
const validRouterInfoMap: Record<string, RouterInfo> = {};

Expand All @@ -31,6 +36,9 @@ export type MethodTypeInfo = {
params: string[];
} & Partial<Record<RequestMethod, TypeInfo>>;

/**
* 获取 dataKey 对应的字段的名称
*/
function getDataKeyName(key: string): DataKey {
switch (key) {
case 'params':
Expand All @@ -45,6 +53,9 @@ function getDataKeyName(key: string): DataKey {
}
}

/**
* 通过字符串路径获取 params
*/
function getRouterParams(path: string) {
const splitPath = path.split('/');
const params: string[] = [];
Expand All @@ -56,13 +67,19 @@ function getRouterParams(path: string) {
return params;
}

/**
* 获取 dataKey 对应的 schema
*/
function getRouterDataSchema(dataKey: string, schema: RouterDataSchema) {
if (dataKey === 'response') {
return schema.response?.['200'] || schema.response?.default || {};
}
return schema[dataKey];
}

/**
* 解析 methodSchema 中对应 dataKey 的类型
*/
function parseRouterDataSchema(methodSchema: RouterDataSchema) {
const typeInfo: TypeInfo = {};
for (const dataKey in methodSchema) {
Expand All @@ -73,6 +90,9 @@ function parseRouterDataSchema(methodSchema: RouterDataSchema) {
return typeInfo;
}

/**
* 解析接口 methodSchema
*/
function parseMethodSchema(schemaMap: RouterSchema) {
const methodTypeInfo: Partial<Record<RequestMethod, TypeInfo>> = {};
for (const method in schemaMap) {
Expand All @@ -82,6 +102,9 @@ function parseMethodSchema(schemaMap: RouterSchema) {
return methodTypeInfo;
}

/**
* 将 routerInfoMap 转换为 typeInfoList
*/
function parseTypeInfo(routerInfoMap: Record<string, RouterInfo>) {
const typeInfoList: MethodTypeInfo[] = [];
for (const stringPath in routerInfoMap) {
Expand All @@ -96,11 +119,17 @@ function parseTypeInfo(routerInfoMap: Record<string, RouterInfo>) {
return typeInfoList;
}

/**
* 获取请求函数的函数名
*/
function getMethodName(method: string, path: string) {
const splitPath = path.split('/').filter(item => item && !item.startsWith(':'));
return camelCase(`${method}/${splitPath.join('/')}`);
}

/**
* 获取请求函数的入参类型
*/
function getMethodOptionsType(typeInfo: TypeInfo) {
const optionsType: string[] = [];
for (const dataKey in typeInfo) {
Expand All @@ -115,6 +144,11 @@ interface APIInfo {
request: string;
}

/**
* 将 typeInfo 解析为 api 信息
*
* 包含类型和请求方法
*/
function parseAPIInfo(typeInfoList: MethodTypeInfo[]) {
const apiInfoList: APIInfo[] = [];
typeInfoList.forEach((item) => {
Expand All @@ -132,6 +166,9 @@ function parseAPIInfo(typeInfoList: MethodTypeInfo[]) {
return apiInfoList;
}

/**
* 解析 api 信息为实际代码内容
*/
function parseAPIContentInfo(apiInfo: APIInfo[]) {
const { type, method } = apiInfo.reduce<{ type: string[]; method: string[] }>((prev, cur) => {
prev.type.push(cur.type);
Expand All @@ -144,14 +181,22 @@ function parseAPIContentInfo(apiInfo: APIInfo[]) {
};
}

/**
* 生成前端请求接口的代码文件
*/
function generateAPIFile(contentInfo: { type: string; method: string }) {
return template.replace(API_TYPE_SYMBOL, contentInfo.type).replace(API_METHOD_SYMBOL, contentInfo.method);
}

/**
* 根据 router-schema 生成前端请求接口的代码文件
*/
export async function generateRequestScriptCli() {
// 禁用日志输出, 因为会载入代码收集依赖, 所以需要将 console 也进行代理, 屏蔽常用输出
setStorage('disabledConsola', true);
globalThis.console = new Proxy({}, { get: () => noop }) as Console;

// 初始化应用
const { port, sourceDir } = await parseConfig();
const { pkgPath } = await getPkgInfo();
const devOptions = { pkgPath, sourceDir, port, isCli: true };
Expand All @@ -165,6 +210,7 @@ export async function generateRequestScriptCli() {

await loadModule(app, router, options);

// 处理文件依赖信息, 生成前端请求文件
const routerInfoMap = getStorage('routerInfoMap');
const validRouterInfoMap = filterValidRouter(routerInfoMap);

Expand Down
3 changes: 3 additions & 0 deletions src/cli/run-prod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { setStorage } from '../storage';
import { consola, getPkgInfo, parseConfig } from '../utils';
import { bootstrap } from './bootstrap';

/**
* 运行打包后的应用
*/
export async function runProd() {
setStorage('isProd', true);

Expand Down
13 changes: 13 additions & 0 deletions src/constant/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
import type { ModuleType } from '../types';

/**
* 需要读取原型的模块类型
*
* 例如 controller 和 service 返回的是 class, 所以想要拿到方法就需要读取他的原型
*/
export const NEED_READ_PROTOTYPE_TYPES = ['controller', 'service'];

/**
* 需要返回模块返回值内容的模块类型
*
* 例如模块返回的是函数, 那想要获取详细类型就需要读取他的返回值类型
*/
export const NEED_RETURN_TYPES = ['config', 'extend', 'routerSchema', 'service', 'middlewares', 'controller', 'router'];

/**
* 模块加载顺序
*/
export const MODULE_LOAD_ORDER: ModuleType[] = ['config', 'extend', 'routerSchema', 'service', 'middlewares', 'controller', 'router'];
31 changes: 31 additions & 0 deletions src/define.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,42 +10,73 @@ interface DefineMiddlewareNeedWrapOptions extends DefineMiddlewareOptions {
needWrap: true;
}

/**
* 定义一个中间件
*/
export function defineMiddleware<C extends (options: TeeOptions<'middleware'>) => (...args: any[]) => TeeKoa.Middleware>(callback: C, options: DefineMiddlewareNeedWrapOptions): C;
export function defineMiddleware<C extends (options: TeeOptions<'middleware'>) => TeeKoa.Middleware>(callback: C): C;
export function defineMiddleware<C extends (options: TeeOptions<'middleware'>) => (...args: any[]) => any>(callback: C, _options?: DefineMiddlewareOptions): C {
return callback;
}

/**
* 定义一个 config
*/
export function defineConfig<C extends (options: TeeOptions<'config'>) => Record<string, any>>(callback: C): C {
return callback;
}

/**
* 定义一个 router
*/
export function defineRouter<C extends (options: Required<TeeOptions<'router'>>) => void>(callback: C): C {
return callback;
}

export interface TeeContext extends KoaRouter.RouterContext<Koa.DefaultState, TeeKoa.Context> {}

/**
* 定义一个控制器
*/
export function defineController<C extends (options: TeeOptions<'controller'>) => new (...args: any) => any>(callback: C): C {
return callback;
}

/**
* 定义一个 service
*/
export function defineService<C extends (options: TeeOptions<'service'>) => new (...args: any) => any>(callback: C): C {
return callback;
}

/**
* 定义一个扩展
*/
export function defineExtend<C extends (options: TeeOptions<'extend'>) => any>(callback: C): C {
return callback;
}

/**
* 定义一个 routerSchema
*
* 可用于自动生成前端接口请求文件
*
* TODO: 生成 OpenAPI 规范的请求文档
*/
export function defineRouterSchema<C extends (options: TeeOptions<'routerSchema'>) => Record<string, RouterSchema>>(callback: C): C {
return callback;
}

/**
* 定义一个 tee 配置
*/
export function defineTeeConfig(config: ConfigFile): ConfigFile {
return config;
}

/**
* 定义一个入口
*/
export function defineEntry<C extends (app: TeeKoa.Application, router: KoaRouter) => void>(callback: C): C {
return callback;
}
Loading