feat: app manager
This commit is contained in:
@@ -1,22 +1,45 @@
|
||||
import { useFileStore } from '@abearxiong/use-file-store';
|
||||
import glob from 'fast-glob';
|
||||
import fs from 'fs';
|
||||
import { getConfigFile } from '@kevisual/use-config';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
export const appsPath = useFileStore('apps', { needExists: true });
|
||||
|
||||
export const loadAppInfo = async () => {
|
||||
const apps = await glob(`${appsPath}/*/\.config.json`, {
|
||||
cwd: appsPath,
|
||||
});
|
||||
const result = apps.map((app) => {
|
||||
const json = fs.readFileSync(path.join(appsPath, app), 'utf-8');
|
||||
const pkg = JSON.parse(json);
|
||||
const { name, version, app: appInfo } = pkg;
|
||||
if (!name || !version || !appInfo) {
|
||||
throw new Error('Invalid package.json');
|
||||
}
|
||||
return { key: app.split('/')[0], ...appInfo };
|
||||
});
|
||||
return result;
|
||||
export type AppInfoConfig = {
|
||||
list: any[];
|
||||
[key: string]: any;
|
||||
};
|
||||
/**
|
||||
* 加载应用信息
|
||||
* @returns
|
||||
*/
|
||||
export const loadAppInfo = async (): Promise<AppInfoConfig> => {
|
||||
const pkgs = getConfigFile();
|
||||
let configFile = getConfigFile('apps.config.json');
|
||||
if (!pkgs) {
|
||||
console.error('未找到配置文件');
|
||||
return;
|
||||
}
|
||||
const basePath = path.dirname(pkgs);
|
||||
if (!configFile) {
|
||||
configFile = path.join(basePath, 'apps.config.json');
|
||||
fs.writeFileSync(configFile, JSON.stringify({}));
|
||||
return { list: [] };
|
||||
}
|
||||
const config = fs.readFileSync(configFile, 'utf-8');
|
||||
return JSON.parse(config);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* 保存应用信息
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const saveAppInfo = async (data: any) => {
|
||||
const configFile = getConfigFile('apps.config.json');
|
||||
if (!configFile) {
|
||||
return;
|
||||
}
|
||||
fs.writeFileSync(configFile, JSON.stringify(data));
|
||||
};
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import fs from 'fs';
|
||||
export const fileIsExist = (filePath: string): boolean => {
|
||||
try {
|
||||
// 使用 F_OK 检查文件或目录是否存在
|
||||
fs.accessSync(filePath, fs.constants.F_OK);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from './app-file.ts';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { minioClient } from '@/app.ts';
|
||||
import { bucketName } from '@/modules/minio.ts';
|
||||
import { fileIsExist } from '../lib/file.ts';
|
||||
import { fileIsExist } from '@kevisual/use-config';
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import * as tar from 'tar';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { fileIsExist } from '../lib/file.ts';
|
||||
import { fileIsExist } from '@kevisual/use-config';
|
||||
|
||||
import { useFileStore } from '@abearxiong/use-file-store';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
@@ -1,20 +1,32 @@
|
||||
import { App } from '@kevisual/router';
|
||||
import { loadAppInfo, AppInfoConfig, saveAppInfo } from '../lib/app-file.ts';
|
||||
import { fork } from 'child_process';
|
||||
|
||||
export enum AppType {
|
||||
SystemApp = 'system-app',
|
||||
MicroApp = 'micro-app',
|
||||
GatewayApp = 'gateway-app',
|
||||
}
|
||||
export type AppInfo = {
|
||||
status?: 'active' | 'inactive'; // 默认运行状态
|
||||
status?: 'inactive' | 'runing' | 'stop' | 'error'; // 运行状态
|
||||
key: string;
|
||||
type?: AppType; // 默认类型
|
||||
entry?: string; // 入口文件
|
||||
path?: string; // 文件路径
|
||||
timestamp?: number; // 时间戳, 每次更新更新时间戳
|
||||
process?: any; // 进程
|
||||
};
|
||||
type managerOptions = {
|
||||
mainApp: App;
|
||||
};
|
||||
|
||||
export class Manager<T extends AppInfo = any> {
|
||||
apps: Map<string, T>;
|
||||
constructor() {
|
||||
mainApp: App;
|
||||
appInfo: AppInfoConfig;
|
||||
constructor(opts: managerOptions) {
|
||||
this.apps = new Map();
|
||||
this.mainApp = opts.mainApp;
|
||||
this.appInfo = {} as any;
|
||||
}
|
||||
/**
|
||||
* 检查key是否存在
|
||||
@@ -28,10 +40,90 @@ export class Manager<T extends AppInfo = any> {
|
||||
* 获取app信息
|
||||
* @param key
|
||||
*/
|
||||
add(app: T) {
|
||||
async loadApp(app: T) {
|
||||
const mainApp = this.mainApp;
|
||||
this.apps.set(app.key, app);
|
||||
if (app.status === 'inactive') {
|
||||
return;
|
||||
}
|
||||
const entry = app.entry + `?timestamp=${app?.timestamp}`;
|
||||
// 注册路由
|
||||
if (app.type === AppType.MicroApp) {
|
||||
const process = fork(entry, [], {});
|
||||
app.process = process;
|
||||
} else if (app.type === AppType.SystemApp) {
|
||||
const module = await import(entry);
|
||||
if (module.loadApp) {
|
||||
await module.loadApp?.(mainApp);
|
||||
}
|
||||
} else if (app.type === AppType.GatewayApp) {
|
||||
console.log('gateway app not support');
|
||||
}
|
||||
}
|
||||
load() {
|
||||
/**
|
||||
* create new app info
|
||||
* @param app
|
||||
*/
|
||||
async saveAppInfo(app: T, newTimeData = false) {
|
||||
const list = this.appInfo.list || [];
|
||||
if (newTimeData) {
|
||||
app.timestamp = Date.now();
|
||||
}
|
||||
const { process, ...info } = app;
|
||||
list.push(info);
|
||||
this.appInfo.list = list;
|
||||
await saveAppInfo(this.appInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化应用的时候加载
|
||||
*/
|
||||
async load() {
|
||||
// 从apps文件夹列表当中中加载app信息
|
||||
const appInfos = await loadAppInfo();
|
||||
const list = appInfos?.list || [];
|
||||
for (const app of list) {
|
||||
try {
|
||||
this.loadApp(app);
|
||||
} catch (e) {
|
||||
console.error('load app', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
async add(app: T) {
|
||||
if (this.checkKey(app.key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.loadApp(app);
|
||||
await this.saveAppInfo(app, true);
|
||||
}
|
||||
// 启动
|
||||
async start(key: string) {
|
||||
const app = this.apps.get(key);
|
||||
if (!app) {
|
||||
return;
|
||||
}
|
||||
if (app.status === 'runing') {
|
||||
return;
|
||||
}
|
||||
app.status = 'runing';
|
||||
this.loadApp(app);
|
||||
await this.saveAppInfo(app);
|
||||
}
|
||||
// 停止
|
||||
async stop(key: string) {
|
||||
const app = this.apps.get(key);
|
||||
if (!app) {
|
||||
return;
|
||||
}
|
||||
if (app.status === 'stop') {
|
||||
return;
|
||||
}
|
||||
app.status = 'stop';
|
||||
if (app.process) {
|
||||
app.process.kill();
|
||||
}
|
||||
await this.saveAppInfo(app);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user