add install base

This commit is contained in:
2025-05-17 03:32:38 +08:00
parent 717e434ce0
commit 035ddc248c
28 changed files with 667 additions and 260 deletions

View File

@@ -1,11 +1,15 @@
import { App } from '@kevisual/router';
import { HttpsPem } from '@/module/assistant/https/sign.ts';
// import { AssistantConfig } from '@/module/assistant/index.ts';
import { AssistantInit } from '@/services/init/index.ts';
export const configDir = AssistantInit.detectConfigDir();
import { AssistantInit, parseHomeArg } from '@/services/init/index.ts';
import { configDir as HomeConfigDir } from '@/module/assistant/config/index.ts';
const manualParse = parseHomeArg(HomeConfigDir);
const _configDir = manualParse.configDir;
export const configDir = AssistantInit.detectConfigDir(_configDir);
export const assistantConfig = new AssistantInit({
path: configDir,
init: true,
init: manualParse?.options?.help ? false : true,
});
const httpsPem = new HttpsPem(assistantConfig);

View File

@@ -66,3 +66,24 @@ appManagerCommand
}
console.log('Restart App:', appKey);
});
const pageListCommand = new Command('page-list')
.alias('pl')
.option('-a, --all', '列出前端页面的所有信息')
.description('列出安装的前端页面')
.action(async (opts) => {
const manager = new AssistantApp(assistantConfig);
await manager.loadConfig();
const showInfos = manager.pageList();
if (opts.all) {
console.log('Installed Pages:', showInfos);
} else {
console.log(
'Installed Pages:',
showInfos.map((item) => {
return { app: item.app, user: item.user, version: item.version };
}),
);
}
});
program.addCommand(pageListCommand);

View File

@@ -7,8 +7,10 @@ const command = new Command('server')
.option('-n, --name <name>', '服务名称')
.option('-p, --port <port>', '服务端口')
.option('-s, --start', '是否启动服务')
.option('-i, --home', '是否以home方式运行')
.action((options) => {
const { port } = options;
const [_interpreter, execPath] = process.argv;
const shellCommands = [];
if (options.daemon) {
shellCommands.push('-d');
@@ -22,10 +24,23 @@ const command = new Command('server')
if (port) {
shellCommands.push(`-p ${port}`);
}
console.log(`Assistant server shell command: asst-server ${shellCommands.join(' ')}`);
const child = spawnSync('asst-server', shellCommands, {
stdio: 'inherit',
shell: true,
});
if (options.home) {
shellCommands.push('--home');
}
const basename = _interpreter.split('/').pop();
if (basename.includes('bun')) {
console.log(`Assistant server shell command: bun src/run-server.ts server ${shellCommands.join(' ')}`);
const child = spawnSync(_interpreter, ['src/run-server.ts', ...shellCommands], {
stdio: 'inherit',
shell: true,
});
} else {
console.log(`Assistant server shell command: asst-server ${shellCommands.join(' ')}`);
const child = spawnSync('asst-server', shellCommands, {
stdio: 'inherit',
shell: true,
});
}
});
program.addCommand(command);

View File

@@ -1,34 +1,9 @@
import { AssistantConfig } from '@/module/assistant/index.ts';
import { AssistantConfig, parseHomeArg } from '@/module/assistant/index.ts';
import { configDir as HomeConfigDir } from '@/module/assistant/config/index.ts';
// 手动解析命令行参数
function parseArgs(args: string[]) {
const result: { root?: string; home?: boolean } = {};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
// 处理 root 参数
if (arg === 'root') {
if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
result.root = args[i + 1];
i++; // 跳过下一个参数,因为它是值
}
}
// 处理 home 参数
if (arg === 'home') {
result.home = true;
}
}
return result;
}
const args = process.argv.slice(2);
const options = parseArgs(args);
let _configDir = undefined;
if (options.home) {
_configDir = HomeConfigDir;
} else if (options.root) {
_configDir = options.root;
}
const _configDir = parseHomeArg(HomeConfigDir).configDir;
// console.log('configDir', _configDir);
// process.exit(0);
export const configDir = AssistantConfig.detectConfigDir(_configDir);

View File

@@ -3,7 +3,7 @@ import { homedir } from 'os';
import fs from 'fs';
import { checkFileExists, createDir } from '../file/index.ts';
import { ProxyInfo } from '../proxy/proxy.ts';
import dotenv from 'dotenv';
/**
* 助手配置文件路径, 全局配置文件目录
*/
@@ -18,7 +18,7 @@ export const initConfig = (configRootPath: string) => {
const configDir = createDir(path.join(configRootPath, 'assistant-app'));
const configPath = path.join(configDir, 'assistant-config.json');
const pageConfigPath = path.join(configDir, 'assistant-page-config.json');
const pageDir = createDir(path.join(configDir, 'page'));
const pagesDir = createDir(path.join(configDir, 'pages'));
const appsDir = createDir(path.join(configDir, 'apps'));
const appsConfigPath = path.join(configDir, 'assistant-apps-config.json');
const appPidPath = path.join(configDir, 'assistant-app.pid');
@@ -43,7 +43,7 @@ export const initConfig = (configRootPath: string) => {
/**
* 应用目录, 前端应用目录
*/
pageDir,
pagesDir,
/**
* 应用配置文件路径, assistant-page-config.json
*/
@@ -207,6 +207,20 @@ export class AssistantConfig {
getAppList() {
return this.getPageConfig().list;
}
getEnvConfig() {
const envConfigPath = this.configPath.envConfigPath;
if (!checkFileExists(envConfigPath)) {
return {};
}
const envConfig = dotenv.parse(fs.readFileSync(envConfigPath));
return envConfig;
}
setEnvConfig(config: string) {
const envConfigPath = this.configPath.envConfigPath;
fs.writeFileSync(envConfigPath, config);
const envConfig = dotenv.parse(fs.readFileSync(envConfigPath));
return envConfig;
}
/**
* process.env.ASSISTANT_CONFIG_DIR || process.cwd()
* configDir是助手配置文件目录文件内部包函
@@ -245,3 +259,58 @@ export class AssistantConfig {
type AppConfig = {
list: any[];
};
export function parseArgs(args: string[]) {
const result: { root?: string; home?: boolean; help?: boolean } = {};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
// 处理 root 参数
if (arg === '--root') {
if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
result.root = args[i + 1];
i++; // 跳过下一个参数,因为它是值
}
}
// 处理 home 参数
if (arg === '--home') {
result.home = true;
}
if (arg === '--help' || arg === '-h') {
result.help = true;
}
}
return result;
}
/**
* 手动解析命令行参数
* @param homedir
* @returns
*/
export const parseHomeArg = (homedir?: string) => {
const args = process.argv.slice(2);
const options = parseArgs(args);
let _configDir = undefined;
if (options.home && homedir) {
_configDir = homedir;
} else if (options.root) {
_configDir = options.root;
}
return {
options,
configDir: _configDir,
};
};
export const parseHelpArg = () => {
const args = process.argv.slice(2);
const options = parseArgs(args);
return !!options?.help;
};
export const parseIfJson = (content: string) => {
try {
return JSON.parse(content);
} catch (error) {
console.error('parseIfJson', error);
return {};
}
};

View File

@@ -34,6 +34,7 @@ export class HttpsPem {
return pemDir;
}
getCert() {
if (!this.assistantConfig.init) return;
const pemDir = this.getPemDir();
const pemPath = {
key: path.join(pemDir, 'https-private-key.pem'),

View File

@@ -1,19 +1,41 @@
import { Manager } from '@kevisual/local-app-manager/manager';
import { AssistantConfig } from '@/module/assistant/index.ts';
import type { AssistantConfig } from '@/module/assistant/index.ts';
import { parseIfJson } from '@/module/assistant/index.ts';
import path from 'node:path';
import fs from 'node:fs';
import glob from 'fast-glob';
export class AssistantApp extends Manager {
config: AssistantConfig;
pagesPath: string;
constructor(config: AssistantConfig) {
config.checkMounted();
const appsPath = config?.configPath?.appsDir || path.join(process.cwd(), 'apps');
const pagesPath = config?.configPath?.pagesDir || path.join(process.cwd(), 'pages');
const appsConfigPath = config.configPath?.appsConfigPath;
const configFimename = path.basename(appsConfigPath || '');
super({
appsPath,
configFilename: configFimename,
});
this.pagesPath = pagesPath;
this.config = config;
}
pageList() {
const pages = glob.sync('*/*/package.json', {
cwd: this.pagesPath,
onlyFiles: true,
});
const pagesParse = pages.map((page) => {
const [user, app] = page.split('/');
const contentStr = fs.readFileSync(path.join(this.pagesPath, page), 'utf-8');
const content = parseIfJson(contentStr);
return {
user,
app,
version: content?.version,
content,
};
});
return pagesParse;
}
}

View File

@@ -1,35 +1,6 @@
export class Logger {
level: string;
constructor(level: string) {
this.level = level;
}
info(message: string, data?: any, ...args: any[]) {
if (this.level === 'info') {
console.log(`INFO: ${message}`, data, ...args);
}
}
error(message: string, data?: any, ...args: any[]) {
if (this.level === 'error') {
console.error(`ERROR: ${message}`, data, ...args);
}
}
warn(message: string, data?: any, ...args: any[]) {
if (this.level === 'warn') {
console.warn(`WARN: ${message}`, data, ...args);
}
}
debug(message: string, data?: any, ...args: any[]) {
if (this.level === 'debug') {
console.debug(`DEBUG: ${message}`, data, ...args);
}
}
log(message: string, ...args: any[]) {
if (this.level === 'log') {
console.log(`LOG: ${message}`, ...args);
}
}
}
const logger = new Logger(process.env.LOG_LEVEL || 'info');
import { Logger } from '@kevisual/logger';
const level = process.env.LOG_LEVEL || 'info';
const logger = new Logger({ level: level as any });
export const console = {
log: logger.info,

View File

@@ -16,14 +16,6 @@ const ls = new Command('ls').description('List files in the current directory').
});
program.addCommand(ls);
const home = new Command('home').description('启动以全局目录').action(() => {}); // @ts-ignore
program.addCommand(home);
const root = new Command('root')
.argument('<path>', '自定义启动路径')
.description('自定义启动路径')
.action(() => {}); // @ts-ignore
program.addCommand(root);
export { program, Command, assistantConfig };
/**

View File

@@ -6,6 +6,7 @@ app
.route({
path: 'config',
description: '获取配置',
middleware: ['auth'],
})
.define(async (ctx) => {
ctx.body = assistantConfig.getCacheAssistantConfig();
@@ -17,6 +18,7 @@ app
path: 'config',
key: 'set',
description: '设置配置',
middleware: ['auth'],
})
.define(async (ctx) => {
const { data } = ctx.query;

View File

@@ -1,2 +1,13 @@
import './config/index.ts'
import './shop-install/index.ts'
import { app } from '../app.ts';
import './config/index.ts';
import './shop-install/index.ts';
app
.route({
path: 'auth',
id: 'auth',
})
.define(async (ctx) => {
//
})
.addTo(app);

View File

@@ -23,6 +23,7 @@ app
.route({
path: 'shop',
key: 'list-installed',
middleware: ['auth'],
})
.define(async (ctx) => {
// https://localhost:51015/client/router?path=shop&key=list-installed
@@ -35,6 +36,7 @@ app
.route({
path: 'shop',
key: 'install',
middleware: ['auth'],
})
.define(async (ctx) => {
// https://localhost:51015/client/router?path=shop&key=install
@@ -51,6 +53,7 @@ app
.route({
path: 'shop',
key: 'uninstall',
middleware: ['auth'],
})
.define(async (ctx) => {
// https://localhost:51015/client/router?path=shop&key=uninstall

View File

@@ -1,3 +1,3 @@
import { runServer } from "./server.ts";
import { runParser } from './server.ts';
runServer();
runParser(process.argv);

View File

@@ -1,10 +1,11 @@
import { app } from './app.ts';
import { app, assistantConfig } from './app.ts';
import { proxyRoute, proxyWs } from './services/proxy/proxy-page-index.ts';
import './routes/index.ts';
import getPort, { portNumbers } from 'get-port';
import { program } from 'commander';
import { spawnSync } from 'child_process';
import chalk from 'chalk';
export const runServer = async (port?: number) => {
let _port: number | undefined;
if (port) {
@@ -39,6 +40,7 @@ program
.option('-n, --name <name>', '服务名称', 'assistant-server')
.option('-p, --port <port>', '服务端口')
.option('-s, --start', '是否启动服务')
.option('-i, --home', 'home目录')
.action(async (options) => {
// console.log('当前执行路径:', execPath, inte);
if (options.daemon) {
@@ -49,6 +51,9 @@ program
if (port) {
pm2Command += ` -p ${port}`;
}
if (options.home) {
pm2Command += ` --home`;
}
const result = spawnSync(pm2Command, {
shell: true,
stdio: 'inherit',
@@ -59,7 +64,7 @@ program
}
console.log('以守护进程方式运行');
} else if (options.start) {
console.log('启动服务');
console.log('启动服务', chalk.green(assistantConfig.configDir));
const server = await runServer(options.port);
}
});

View File

@@ -57,7 +57,7 @@ export class AppDownload {
const configDir = this.config.configDir;
this.config?.checkMounted();
const appsDir = this.config.configPath?.appsDir;
const pageDir = this.config.configPath?.pageDir;
const pagesDir = this.config.configPath?.pagesDir;
if (!id) {
throw new Error('应用名称不能为空');
}
@@ -69,7 +69,7 @@ export class AppDownload {
}
const appName = opts?.appName || id.split('/').pop();
if (type === 'web') {
args.push('-o', pageDir);
args.push('-o', pagesDir);
} else if (type === 'app') {
args.push('-o', path.join(appsDir, appName));
} else {
@@ -96,7 +96,7 @@ export class AppDownload {
const appName = opts?.appName || id.split('/').pop();
this.config?.checkMounted();
const appsDir = this.config.configPath?.appsDir;
const pageDir = this.config.configPath?.pageDir;
const pagesDir = this.config.configPath?.pagesDir;
if (!id) {
throw new Error('应用名称不能为空');
}
@@ -104,7 +104,7 @@ export class AppDownload {
let isDelete = false;
if (type === 'web') {
// 直接删除路径就行
const pagePath = path.join(pageDir, id);
const pagePath = path.join(pagesDir, id);
deletePath = pagePath;
} else if (type === 'app') {
const appPath = path.join(appsDir, appName);

View File

@@ -1,8 +1,9 @@
import fs from 'node:fs';
import path from 'node:path';
import { checkFileExists, AssistantConfig, AssistantConfigData } from '@/module/assistant/index.ts';
import { checkFileExists, AssistantConfig, AssistantConfigData, parseHomeArg, parseHelpArg } from '@/module/assistant/index.ts';
import { chalk } from '@/module/chalk.ts';
import { HttpsPem } from '@/module/assistant/https/sign.ts';
export { parseHomeArg, parseHelpArg };
export type AssistantInitOptions = {
path?: string;
init?: boolean;
@@ -30,7 +31,8 @@ export class AssistantInit extends AssistantConfig {
super.init();
} else {
super.init();
console.log(chalk.yellow('助手路径已存在'));
const assistantConfig = this;
console.log(chalk.yellow('助手路径已存在'), chalk.green(assistantConfig.configDir));
}
this.createAssistantConfig();
this.createEnvConfig();

View File

@@ -43,7 +43,7 @@ export class LocalProxy {
}
}
init() {
const frontAppDir = this.assistantConfig.configPath?.pageDir;
const frontAppDir = this.assistantConfig.configPath?.pagesDir;
if (frontAppDir) {
const userList = fs.readdirSync(frontAppDir);
const localProxyProxyList: ProxyType[] = [];

View File

@@ -8,7 +8,7 @@ const localProxy = new LocalProxy({
});
export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResponse) => {
const _assistantConfig = assistantConfig.getCacheAssistantConfig();
const appDir = assistantConfig.configPath?.pageDir;
const appDir = assistantConfig.configPath?.pagesDir;
const url = new URL(req.url, 'http://localhost');
const pathname = url.pathname;
if (pathname === '/' && _assistantConfig?.home) {