init
This commit is contained in:
parent
9eb4d06939
commit
bcc12209e0
3
assistant/.gitignore
vendored
3
assistant/.gitignore
vendored
@ -6,3 +6,6 @@ dist
|
|||||||
pack-dist
|
pack-dist
|
||||||
|
|
||||||
assistant-app
|
assistant-app
|
||||||
|
|
||||||
|
.env*
|
||||||
|
!.env*example
|
4
assistant/bin/assistant-server.js
Normal file
4
assistant/bin/assistant-server.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
import { runParser } from '../dist/assistant-server.mjs';
|
||||||
|
|
||||||
|
runParser(process.argv);
|
4
assistant/bin/assistant.js
Normal file
4
assistant/bin/assistant.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
import { runParser } from '../dist/assistant.mjs';
|
||||||
|
|
||||||
|
runParser(process.argv);
|
@ -17,3 +17,17 @@ await Bun.build({
|
|||||||
},
|
},
|
||||||
env: 'ENVISION_*',
|
env: 'ENVISION_*',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await Bun.build({
|
||||||
|
target: 'node',
|
||||||
|
format: 'esm',
|
||||||
|
entrypoints: ['./src/server.ts'],
|
||||||
|
outdir: './dist',
|
||||||
|
naming: {
|
||||||
|
entry: 'assistan-server.mjs',
|
||||||
|
},
|
||||||
|
define: {
|
||||||
|
ENVISION_VERSION: JSON.stringify(pkg.version),
|
||||||
|
},
|
||||||
|
env: 'ENVISION_*',
|
||||||
|
});
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
],
|
],
|
||||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"packageManager": "pnpm@10.7.0",
|
"packageManager": "pnpm@10.9.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
@ -19,24 +19,36 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun run src/run.ts ",
|
"dev": "bun run src/run.ts ",
|
||||||
"dev:serve": "bun --watch src/serve.ts",
|
"dev:server": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 bun --watch src/server.ts",
|
||||||
"build": "rimraf dist && bun run bun.config.mjs"
|
"build": "rimraf dist && bun run bun.config.mjs"
|
||||||
},
|
},
|
||||||
|
"bin": {
|
||||||
|
"ev-assistant": "bin/assistant.js",
|
||||||
|
"ev-asst": "bin/assistant.js"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@kevisual/load": "^0.0.6",
|
"@kevisual/load": "^0.0.6",
|
||||||
|
"@kevisual/local-app-manager": "^0.1.16",
|
||||||
"@kevisual/query": "0.0.17",
|
"@kevisual/query": "0.0.17",
|
||||||
"@kevisual/query-login": "0.0.5",
|
"@kevisual/query-login": "0.0.5",
|
||||||
"@kevisual/router": "^0.0.13",
|
"@kevisual/router": "^0.0.13",
|
||||||
"@kevisual/use-config": "^1.0.11",
|
"@kevisual/use-config": "^1.0.11",
|
||||||
"@types/bun": "^1.2.10",
|
"@types/bun": "^1.2.10",
|
||||||
"@types/node": "^22.14.1",
|
"@types/lodash-es": "^4.17.12",
|
||||||
|
"@types/node": "^22.15.2",
|
||||||
"@types/send": "^0.17.4",
|
"@types/send": "^0.17.4",
|
||||||
|
"@types/ws": "^8.18.1",
|
||||||
"chalk": "^5.4.1",
|
"chalk": "^5.4.1",
|
||||||
"commander": "^13.1.0",
|
"commander": "^13.1.0",
|
||||||
"inquirer": "^12.5.2",
|
"cross-env": "^7.0.3",
|
||||||
|
"inquirer": "^12.6.0",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"nanoid": "^5.1.5",
|
||||||
"pino": "^9.6.0",
|
"pino": "^9.6.0",
|
||||||
"pino-pretty": "^13.0.0",
|
"pino-pretty": "^13.0.0",
|
||||||
"send": "^1.2.0",
|
"send": "^1.2.0",
|
||||||
|
"supports-color": "^10.0.0",
|
||||||
|
"ws": "npm:@kevisual/ws",
|
||||||
"zustand": "^5.0.3"
|
"zustand": "^5.0.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -44,5 +56,11 @@
|
|||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"dayjs": "^1.11.13"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"ws": "npm:@kevisual/ws"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,13 +1,7 @@
|
|||||||
import { App } from '@kevisual/router';
|
import { App } from '@kevisual/router';
|
||||||
import { AssistantConfig } from '@/module/assistant/index.ts';
|
|
||||||
import { HttpsPem } from '@/module/assistant/https/sign.ts';
|
import { HttpsPem } from '@/module/assistant/https/sign.ts';
|
||||||
import path from 'node:path';
|
import { assistantConfig } from '@/config.ts';
|
||||||
|
export { assistantConfig };
|
||||||
export const configDir = path.resolve(process.env.assistantConfigDir || process.cwd());
|
|
||||||
export const assistantConfig = new AssistantConfig({
|
|
||||||
configDir,
|
|
||||||
init: true,
|
|
||||||
});
|
|
||||||
const httpsPem = new HttpsPem(assistantConfig);
|
const httpsPem = new HttpsPem(assistantConfig);
|
||||||
export const app = new App({
|
export const app = new App({
|
||||||
serverOptions: {
|
serverOptions: {
|
||||||
|
68
assistant/src/command/app-manager/index.ts
Normal file
68
assistant/src/command/app-manager/index.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { AssistantApp } from '@/module/assistant/index.ts';
|
||||||
|
import { program, Command, assistantConfig } from '@/program.ts';
|
||||||
|
|
||||||
|
const appManagerCommand = new Command('app-manager').alias('am').description('Manage Assistant Apps 管理本地的应用模块');
|
||||||
|
program.addCommand(appManagerCommand);
|
||||||
|
|
||||||
|
appManagerCommand
|
||||||
|
.command('list')
|
||||||
|
.description('List all installed apps')
|
||||||
|
.action(async () => {
|
||||||
|
const manager = new AssistantApp(assistantConfig);
|
||||||
|
await manager.loadConfig();
|
||||||
|
const showInfos = manager.getAllAppShowInfo();
|
||||||
|
console.log('Installed Apps:', showInfos);
|
||||||
|
});
|
||||||
|
|
||||||
|
appManagerCommand
|
||||||
|
.command('detect')
|
||||||
|
.description('Detect all installed apps')
|
||||||
|
.action(async () => {
|
||||||
|
const manager = new AssistantApp(assistantConfig);
|
||||||
|
await manager.loadConfig();
|
||||||
|
const showInfos = await manager.detectApp();
|
||||||
|
if (showInfos === true) {
|
||||||
|
const showInfos = manager.getAllAppShowInfo();
|
||||||
|
console.log('Installed Apps:', showInfos);
|
||||||
|
} else {
|
||||||
|
console.log('Install New Apps:', showInfos);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
appManagerCommand
|
||||||
|
.command('start')
|
||||||
|
.argument('<app-key-name>', '应用的 key 名称')
|
||||||
|
.action(async (appKey: string) => {
|
||||||
|
const manager = new AssistantApp(assistantConfig);
|
||||||
|
await manager.loadConfig();
|
||||||
|
manager.start(appKey);
|
||||||
|
console.log('Start App:', appKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
appManagerCommand
|
||||||
|
.command('stop')
|
||||||
|
.argument('<app-key-name>', '应用的 key 名称')
|
||||||
|
.action(async (appKey: string) => {
|
||||||
|
const manager = new AssistantApp(assistantConfig);
|
||||||
|
try {
|
||||||
|
await manager.loadConfig();
|
||||||
|
await manager.stop(appKey);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
console.log('Stop App:', appKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
appManagerCommand
|
||||||
|
.command('restart')
|
||||||
|
.argument('<app-key-name>', '应用的 key 名称')
|
||||||
|
.action(async (appKey: string) => {
|
||||||
|
const manager = new AssistantApp(assistantConfig);
|
||||||
|
try {
|
||||||
|
await manager.loadConfig();
|
||||||
|
await manager.restart(appKey);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
console.log('Restart App:', appKey);
|
||||||
|
});
|
7
assistant/src/config.ts
Normal file
7
assistant/src/config.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { AssistantConfig } from '@/module/assistant/index.ts';
|
||||||
|
|
||||||
|
export const configDir = AssistantConfig.detectConfigDir();
|
||||||
|
export const assistantConfig = new AssistantConfig({
|
||||||
|
configDir,
|
||||||
|
init: true,
|
||||||
|
});
|
@ -1,5 +1,6 @@
|
|||||||
import { program, runProgram } from '@/program.ts';
|
import { program, runProgram } from '@/program.ts';
|
||||||
import './command/init/index.ts';
|
import './command/init/index.ts';
|
||||||
|
import './command/app-manager/index.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过命令行解析器解析参数
|
* 通过命令行解析器解析参数
|
||||||
|
@ -17,11 +17,12 @@ const configDir = createDir(path.join(homedir(), '.config/envision/assistant-app
|
|||||||
export const initConfig = (configRootPath: string) => {
|
export const initConfig = (configRootPath: string) => {
|
||||||
const configDir = createDir(path.join(configRootPath, 'assistant-app'));
|
const configDir = createDir(path.join(configRootPath, 'assistant-app'));
|
||||||
const configPath = path.join(configDir, 'assistant-config.json');
|
const configPath = path.join(configDir, 'assistant-config.json');
|
||||||
const appConfigPath = path.join(configDir, 'assistant-app-config.json');
|
const pageConfigPath = path.join(configDir, 'assistant-page-config.json');
|
||||||
const appDir = createDir(path.join(configDir, 'frontend'));
|
const pageDir = createDir(path.join(configDir, 'page'));
|
||||||
const serviceDir = createDir(path.join(configDir, 'services'));
|
const appsDir = createDir(path.join(configDir, 'apps'));
|
||||||
const serviceConfigPath = path.join(serviceDir, 'assistant-service-config.json');
|
const appsConfigPath = path.join(appsDir, 'assistant-apps-config.json');
|
||||||
const appPidPath = path.join(configDir, 'assistant-app.pid');
|
const appPidPath = path.join(configDir, 'assistant-app.pid');
|
||||||
|
const envConfigPath = path.join(configDir, '.env');
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
* 助手配置文件路径
|
* 助手配置文件路径
|
||||||
@ -34,30 +35,34 @@ export const initConfig = (configRootPath: string) => {
|
|||||||
/**
|
/**
|
||||||
* 服务目录, 后端服务目录
|
* 服务目录, 后端服务目录
|
||||||
*/
|
*/
|
||||||
serviceDir,
|
appsDir,
|
||||||
/**
|
/**
|
||||||
* 服务配置文件路径 assistant-service-config.json
|
* 服务配置文件路径 assistant-service-config.json
|
||||||
*/
|
*/
|
||||||
serviceConfigPath,
|
appsConfigPath,
|
||||||
/**
|
/**
|
||||||
* 应用目录, 前端应用目录
|
* 应用目录, 前端应用目录
|
||||||
*/
|
*/
|
||||||
appDir,
|
pageDir,
|
||||||
/**
|
/**
|
||||||
* 应用配置文件路径, assistant-app-config.json
|
* 应用配置文件路径, assistant-page-config.json
|
||||||
*/
|
*/
|
||||||
appConfigPath,
|
pageConfigPath,
|
||||||
/**
|
/**
|
||||||
* 应用进程pid文件路径
|
* 应用进程pid文件路径
|
||||||
*/
|
*/
|
||||||
appPidPath,
|
appPidPath,
|
||||||
|
/**
|
||||||
|
* 环境变量配置文件路径
|
||||||
|
*/
|
||||||
|
envConfigPath,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export type ReturnInitConfigType = ReturnType<typeof initConfig>;
|
export type ReturnInitConfigType = ReturnType<typeof initConfig>;
|
||||||
|
|
||||||
type AssistantConfigData = {
|
type AssistantConfigData = {
|
||||||
pageApi?: string; // https://kevisual.silkyai.cn
|
pageApi?: string; // https://kevisual.silkyai.cn
|
||||||
proxy?: { user: string; key: string; path: string }[];
|
proxy?: ProxyInfo[];
|
||||||
apiProxyList?: ProxyInfo[];
|
apiProxyList?: ProxyInfo[];
|
||||||
description?: string;
|
description?: string;
|
||||||
};
|
};
|
||||||
@ -120,25 +125,25 @@ export class AssistantConfig {
|
|||||||
* 应用配置
|
* 应用配置
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
getAppConfig(): AppConfig {
|
getPageConfig(): AppConfig {
|
||||||
const { appConfigPath } = this.configPath;
|
const { pageConfigPath } = this.configPath;
|
||||||
if (!checkFileExists(appConfigPath)) {
|
if (!checkFileExists(pageConfigPath)) {
|
||||||
return {
|
return {
|
||||||
list: [],
|
list: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return JSON.parse(fs.readFileSync(appConfigPath, 'utf8'));
|
return JSON.parse(fs.readFileSync(pageConfigPath, 'utf8'));
|
||||||
}
|
}
|
||||||
setAppConfig(config?: AppConfig) {
|
setAppConfig(config?: AppConfig) {
|
||||||
const _config = this.getAppConfig();
|
const _config = this.getPageConfig();
|
||||||
const _saveConfig = { ..._config, ...config };
|
const _saveConfig = { ..._config, ...config };
|
||||||
const { appConfigPath } = this.configPath;
|
const { pageConfigPath } = this.configPath;
|
||||||
|
|
||||||
fs.writeFileSync(appConfigPath, JSON.stringify(_saveConfig, null, 2));
|
fs.writeFileSync(pageConfigPath, JSON.stringify(_saveConfig, null, 2));
|
||||||
return _saveConfig;
|
return _saveConfig;
|
||||||
}
|
}
|
||||||
assAppConfig(app: any) {
|
assAppConfig(app: any) {
|
||||||
const config = this.getAppConfig();
|
const config = this.getPageConfig();
|
||||||
const assistantConfig = this.getCacheAssistantConfig();
|
const assistantConfig = this.getCacheAssistantConfig();
|
||||||
const _apps = config.list;
|
const _apps = config.list;
|
||||||
const _proxy = assistantConfig.proxy || [];
|
const _proxy = assistantConfig.proxy || [];
|
||||||
@ -166,7 +171,36 @@ export class AssistantConfig {
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
getAppList() {
|
getAppList() {
|
||||||
return this.getAppConfig().list;
|
return this.getPageConfig().list;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* process.env.ASSISTANT_CONFIG_DIR || process.cwd()
|
||||||
|
* configDir是助手配置文件目录,文件内部包函
|
||||||
|
* assistant-config.json 配置文件
|
||||||
|
* assistant-page-config.json 应用配置文件
|
||||||
|
* assistant-app.pid 应用进程pid文件
|
||||||
|
* .env 环境变量配置文件
|
||||||
|
* apps: 服务目录
|
||||||
|
* page: 应用目录
|
||||||
|
* pem: 证书目录
|
||||||
|
* @param configDir
|
||||||
|
*/
|
||||||
|
static detectConfigDir(configDir?: string) {
|
||||||
|
const checkConfigDir = path.resolve(configDir || process.env.ASSISTANT_CONFIG_DIR || process.cwd());
|
||||||
|
const configPath = path.join(checkConfigDir, 'assistant-app');
|
||||||
|
if (checkFileExists(configPath)) {
|
||||||
|
return path.join(checkConfigDir);
|
||||||
|
}
|
||||||
|
const lastConfigPath = path.join(checkConfigDir, '..', 'assistant-app');
|
||||||
|
if (checkFileExists(lastConfigPath)) {
|
||||||
|
return path.join(checkConfigDir, '..');
|
||||||
|
}
|
||||||
|
const lastConfigPath2 = path.join(checkConfigDir, '../..', 'assistant-app');
|
||||||
|
if (checkFileExists(lastConfigPath2)) {
|
||||||
|
return path.join(checkConfigDir, '../..');
|
||||||
|
}
|
||||||
|
// 如果没有找到助手配置文件目录,则返回当前目录, 执行默认创建助手配置文件目录
|
||||||
|
return checkConfigDir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@ import fs from 'node:fs';
|
|||||||
import { AssistantConfig } from '../config/index.ts';
|
import { AssistantConfig } from '../config/index.ts';
|
||||||
import { checkFileExists } from '../file/index.ts';
|
import { checkFileExists } from '../file/index.ts';
|
||||||
import { chalk } from '@/module/chalk.ts';
|
import { chalk } from '@/module/chalk.ts';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
type Attributes = {
|
type Attributes = {
|
||||||
name: string;
|
name: string;
|
||||||
value: string;
|
value: string;
|
||||||
@ -36,24 +38,91 @@ export class HttpsPem {
|
|||||||
const pemPath = {
|
const pemPath = {
|
||||||
key: path.join(pemDir, 'https-private-key.pem'),
|
key: path.join(pemDir, 'https-private-key.pem'),
|
||||||
cert: path.join(pemDir, 'https-cert.pem'),
|
cert: path.join(pemDir, 'https-cert.pem'),
|
||||||
|
config: path.join(pemDir, 'https-config.json'),
|
||||||
|
};
|
||||||
|
const writeCreate = (opts: { key: string; cert: string; data: { createTime: number; expireTime: number } }) => {
|
||||||
|
fs.writeFileSync(pemPath.key, opts.key);
|
||||||
|
fs.writeFileSync(pemPath.cert, opts.cert);
|
||||||
|
fs.writeFileSync(pemPath.config, JSON.stringify(opts.data, null, 2));
|
||||||
};
|
};
|
||||||
if (!checkFileExists(pemPath.key) || !checkFileExists(pemPath.cert)) {
|
if (!checkFileExists(pemPath.key) || !checkFileExists(pemPath.cert)) {
|
||||||
const { key, cert } = this.createCert();
|
const { key, cert, data } = this.createCert();
|
||||||
fs.writeFileSync(pemPath.key, key);
|
writeCreate({ key, cert, data });
|
||||||
fs.writeFileSync(pemPath.cert, cert);
|
console.log(chalk.green('证书创建成功,浏览器需要导入当前证书'));
|
||||||
console.log(chalk.green('证书创建成功'))
|
|
||||||
return {
|
return {
|
||||||
key,
|
key,
|
||||||
cert,
|
cert,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!checkFileExists(pemPath.config)) {
|
||||||
|
const data = this.createExpireData();
|
||||||
|
fs.writeFileSync(pemPath.config, JSON.stringify(data, null, 2));
|
||||||
|
}
|
||||||
const key = fs.readFileSync(pemPath.key, 'utf-8');
|
const key = fs.readFileSync(pemPath.key, 'utf-8');
|
||||||
const cert = fs.readFileSync(pemPath.cert, 'utf-8');
|
const cert = fs.readFileSync(pemPath.cert, 'utf-8');
|
||||||
|
const config = fs.readFileSync(pemPath.config, 'utf-8');
|
||||||
|
let expireTime = 0;
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(config);
|
||||||
|
expireTime = data.expireTime;
|
||||||
|
if (typeof expireTime !== 'number') {
|
||||||
|
throw new Error('expireTime is not a number');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(chalk.red('证书配置文件损坏,重新生成证书'));
|
||||||
|
}
|
||||||
|
const now = new Date().getTime();
|
||||||
|
if (now > expireTime) {
|
||||||
|
this.removeCert();
|
||||||
|
const { key, cert, data } = this.createCert();
|
||||||
|
writeCreate({ key, cert, data });
|
||||||
|
console.log(chalk.green('证书更新成功, 浏览器需要重新导入当前证书'));
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
cert,
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
key,
|
key,
|
||||||
cert,
|
cert,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
createExpireData() {
|
||||||
|
const expireTime = new Date().getTime() + 365 * 24 * 60 * 60 * 1000;
|
||||||
|
const expireDate = dayjs(expireTime).format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
return {
|
||||||
|
description: '手动导入证书到浏览器, https-cert.pem文件, 具体使用教程访问 https://kevisual.cn/root/pem-docs/',
|
||||||
|
createTime: new Date().getTime(),
|
||||||
|
expireDate,
|
||||||
|
expireTime,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* 重新生成证书
|
||||||
|
*/
|
||||||
|
removeCert() {
|
||||||
|
const pemDir = this.getPemDir();
|
||||||
|
const pemPath = {
|
||||||
|
key: path.join(pemDir, 'https-private-key.pem'),
|
||||||
|
cert: path.join(pemDir, 'https-cert.pem'),
|
||||||
|
};
|
||||||
|
const oldPath = {
|
||||||
|
key: path.join(pemDir, 'https-private-key.pem.bak'),
|
||||||
|
cert: path.join(pemDir, 'https-cert.pem.bak'),
|
||||||
|
};
|
||||||
|
if (checkFileExists(pemPath.key)) {
|
||||||
|
fs.renameSync(pemPath.key, oldPath.key);
|
||||||
|
}
|
||||||
|
if (checkFileExists(pemPath.cert)) {
|
||||||
|
fs.renameSync(pemPath.cert, oldPath.cert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* 创建证书
|
||||||
|
* @param attrs 证书属性
|
||||||
|
* @param altNames 证书备用名称
|
||||||
|
*/
|
||||||
createCert(attrs?: Attributes[], altNames?: AltNames[]) {
|
createCert(attrs?: Attributes[], altNames?: AltNames[]) {
|
||||||
const attributes = attrs || [];
|
const attributes = attrs || [];
|
||||||
const altNamesList = altNames || [];
|
const altNamesList = altNames || [];
|
||||||
@ -71,9 +140,13 @@ export class HttpsPem {
|
|||||||
],
|
],
|
||||||
altNamesList,
|
altNamesList,
|
||||||
);
|
);
|
||||||
|
const data = this.createExpireData();
|
||||||
return {
|
return {
|
||||||
key,
|
key,
|
||||||
cert,
|
cert,
|
||||||
|
data: {
|
||||||
|
...data,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,3 +5,5 @@ export * from './file/index.ts';
|
|||||||
export * from './process/index.ts';
|
export * from './process/index.ts';
|
||||||
|
|
||||||
export * from './proxy/index.ts';
|
export * from './proxy/index.ts';
|
||||||
|
|
||||||
|
export * from './local-app-manager/index.ts';
|
@ -0,0 +1,18 @@
|
|||||||
|
import { Manager } from '@kevisual/local-app-manager/manager';
|
||||||
|
import { AssistantConfig } from '@/module/assistant/index.ts';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
export class AssistantApp extends Manager {
|
||||||
|
config: AssistantConfig;
|
||||||
|
|
||||||
|
constructor(config: AssistantConfig) {
|
||||||
|
const appsPath = config.configPath?.appsDir || path.join(process.cwd(), 'apps');
|
||||||
|
const appsConfigPath = config.configPath?.appsConfigPath;
|
||||||
|
const configFimename = path.basename(appsConfigPath || '');
|
||||||
|
super({
|
||||||
|
appsPath,
|
||||||
|
configFilename: configFimename,
|
||||||
|
});
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
export { Manager } from '@kevisual/local-app-manager/manager';
|
||||||
|
export { Pm2Manager } from '@kevisual/local-app-manager/pm2';
|
||||||
|
export { AssistantApp } from './assistant-app.ts';
|
@ -4,6 +4,7 @@ import fs from 'node:fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { ProxyInfo } from './proxy.ts';
|
import { ProxyInfo } from './proxy.ts';
|
||||||
import { checkFileExists } from '../file/index.ts';
|
import { checkFileExists } from '../file/index.ts';
|
||||||
|
import { log } from '@/module/logger.ts';
|
||||||
|
|
||||||
export const fileProxy = (req: http.IncomingMessage, res: http.ServerResponse, proxyApi: ProxyInfo) => {
|
export const fileProxy = (req: http.IncomingMessage, res: http.ServerResponse, proxyApi: ProxyInfo) => {
|
||||||
// url开头的文件
|
// url开头的文件
|
||||||
@ -11,6 +12,9 @@ export const fileProxy = (req: http.IncomingMessage, res: http.ServerResponse, p
|
|||||||
const [user, key, _info] = url.pathname.split('/');
|
const [user, key, _info] = url.pathname.split('/');
|
||||||
const pathname = url.pathname.slice(1);
|
const pathname = url.pathname.slice(1);
|
||||||
const { indexPath = '', target = '', rootPath = process.cwd() } = proxyApi;
|
const { indexPath = '', target = '', rootPath = process.cwd() } = proxyApi;
|
||||||
|
if (!indexPath) {
|
||||||
|
return res.end('Not Found indexPath');
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
// 检测文件是否存在,如果文件不存在,则返回404
|
// 检测文件是否存在,如果文件不存在,则返回404
|
||||||
let filePath = '';
|
let filePath = '';
|
||||||
@ -23,7 +27,7 @@ export const fileProxy = (req: http.IncomingMessage, res: http.ServerResponse, p
|
|||||||
filePath = path.join(rootPath, target, indexPath);
|
filePath = path.join(rootPath, target, indexPath);
|
||||||
exist = checkFileExists(filePath, true);
|
exist = checkFileExists(filePath, true);
|
||||||
}
|
}
|
||||||
console.log('filePath', filePath, exist);
|
log.debug('filePath', { filePath, exist });
|
||||||
|
|
||||||
if (!exist) {
|
if (!exist) {
|
||||||
res.statusCode = 404;
|
res.statusCode = 404;
|
||||||
|
@ -28,10 +28,13 @@ export const createApiProxy = (api: string, paths: string[] = ['/api', '/v1']) =
|
|||||||
return pathList;
|
return pathList;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const apiProxy = (req: http.IncomingMessage, res: http.ServerResponse, proxyApi: ProxyInfo) => {
|
export const httpProxy = (req: http.IncomingMessage, res: http.ServerResponse, proxyApi: ProxyInfo) => {
|
||||||
const { target } = proxyApi;
|
const { target } = proxyApi;
|
||||||
const _u = new URL(req.url, `${target}`);
|
const _u = new URL(req.url, `${target}`);
|
||||||
console.log('proxyApi', { url: req.url, target: _u.href });
|
// console.log('proxyApi', { url: req.url, target: _u.href });
|
||||||
|
if (proxyApi.pathname) {
|
||||||
|
_u.pathname = _u.pathname.replace(proxyApi.path, proxyApi.pathname);
|
||||||
|
}
|
||||||
// 设置代理请求的目标 URL 和请求头
|
// 设置代理请求的目标 URL 和请求头
|
||||||
let header: any = {};
|
let header: any = {};
|
||||||
if (req.headers?.['Authorization'] && !req.headers?.['authorization']) {
|
if (req.headers?.['Authorization'] && !req.headers?.['authorization']) {
|
||||||
@ -41,6 +44,9 @@ export const apiProxy = (req: http.IncomingMessage, res: http.ServerResponse, pr
|
|||||||
// 处理大小写不一致的cookie
|
// 处理大小写不一致的cookie
|
||||||
header.cookie = req.headers['cookie'] || req.headers['Cookie'];
|
header.cookie = req.headers['cookie'] || req.headers['Cookie'];
|
||||||
}
|
}
|
||||||
|
// console.log('host', req.headers.host, proxyApi, _u.href, req.headers.authorization);
|
||||||
|
const origin = req.headers?.origin || req.headers?.referer || 'http://localhost';
|
||||||
|
const cookieHost = new URL(origin).host.split(':')[0];
|
||||||
// 提取req的headers中的非HOST的header
|
// 提取req的headers中的非HOST的header
|
||||||
const headers = Object.keys(req.headers).filter((item) => item && item.toLowerCase() !== 'host');
|
const headers = Object.keys(req.headers).filter((item) => item && item.toLowerCase() !== 'host');
|
||||||
headers.forEach((item) => {
|
headers.forEach((item) => {
|
||||||
@ -54,11 +60,12 @@ export const apiProxy = (req: http.IncomingMessage, res: http.ServerResponse, pr
|
|||||||
}
|
}
|
||||||
header[item] = req.headers[item];
|
header[item] = req.headers[item];
|
||||||
});
|
});
|
||||||
const options: http.RequestOptions = {
|
const options: http.RequestOptions | https.RequestOptions = {
|
||||||
host: _u.hostname,
|
host: _u.hostname,
|
||||||
path: req.url,
|
path: req.url,
|
||||||
method: req.method,
|
method: req.method,
|
||||||
headers: {
|
headers: {
|
||||||
|
['kevisual-origin']: 'assistant',
|
||||||
...header,
|
...header,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -67,12 +74,20 @@ export const apiProxy = (req: http.IncomingMessage, res: http.ServerResponse, pr
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
options.port = _u.port;
|
options.port = _u.port;
|
||||||
}
|
}
|
||||||
|
const isHttps = _u.protocol === 'https:';
|
||||||
const httpProxy = _u.protocol === 'https:' ? https : http;
|
const httpProxy = _u.protocol === 'https:' ? https : http;
|
||||||
|
console.log('httpProxy', { isHttps, target, url: _u.href });
|
||||||
|
if (isHttps) {
|
||||||
|
// @ts-ignore
|
||||||
|
options.rejectUnauthorized = false; // 忽略证书错误
|
||||||
|
}
|
||||||
|
|
||||||
// 创建代理请求
|
// 创建代理请求
|
||||||
const proxyReq = httpProxy.request(options, (proxyRes) => {
|
const proxyReq = httpProxy.request(options, (proxyRes) => {
|
||||||
// Modify the 'set-cookie' headers using rewriteCookieDomain
|
// Modify the 'set-cookie' headers using rewriteCookieDomain
|
||||||
if (proxyRes.headers['set-cookie']) {
|
if (proxyRes.headers['set-cookie']) {
|
||||||
proxyRes.headers['set-cookie'] = proxyRes.headers['set-cookie'].map((cookie) => rewriteCookieDomain(cookie, 'localhost'));
|
proxyRes.headers['set-cookie'] = proxyRes.headers['set-cookie'].map((cookie) => rewriteCookieDomain(cookie, cookieHost));
|
||||||
|
console.log('rewritten set-cookie:', proxyRes.headers['set-cookie']);
|
||||||
}
|
}
|
||||||
// 将代理服务器的响应头和状态码返回给客户端
|
// 将代理服务器的响应头和状态码返回给客户端
|
||||||
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
@ -1,5 +1,5 @@
|
|||||||
export * from './proxy.ts';
|
export * from './proxy.ts';
|
||||||
export * from './file-proxy.ts';
|
export * from './file-proxy.ts';
|
||||||
export { default as send } from 'send';
|
export { default as send } from 'send';
|
||||||
export * from './api-proxy.ts';
|
export * from './http-proxy.ts';
|
||||||
export * from './ws-proxy.ts';
|
export * from './ws-proxy.ts';
|
@ -1,4 +1,7 @@
|
|||||||
export type ProxyInfo = {
|
export type ProxyInfo = {
|
||||||
|
/**
|
||||||
|
* 代理路径, 比如/root/center, 匹配的路径
|
||||||
|
*/
|
||||||
path?: string;
|
path?: string;
|
||||||
/**
|
/**
|
||||||
* 目标地址
|
* 目标地址
|
||||||
@ -7,21 +10,28 @@ export type ProxyInfo = {
|
|||||||
/**
|
/**
|
||||||
* 类型
|
* 类型
|
||||||
*/
|
*/
|
||||||
type?: 'static' | 'dynamic' | 'minio';
|
type?: 'file' | 'dynamic' | 'minio' | 'http';
|
||||||
/**
|
/**
|
||||||
* 是否使用websocket
|
* 目标的 pathname, 默认为请求的url.pathname, 设置了pathname,则会使用pathname作为请求的url.pathname
|
||||||
|
* @default undefined
|
||||||
|
* @example /api/v1/user
|
||||||
|
*/
|
||||||
|
pathname?: string;
|
||||||
|
/**
|
||||||
|
* 是否使用websocket, http
|
||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
ws?: boolean;
|
ws?: boolean;
|
||||||
/**
|
/**
|
||||||
* 首要文件,比如index.html, 设置了首要文件,如果文件不存在,则访问首要文件
|
* 首要文件,比如index.html, type为fileProxy代理有用 设置了首要文件,如果文件不存在,则访问首要文件
|
||||||
*/
|
*/
|
||||||
indexPath?: string;
|
indexPath?: string;
|
||||||
/**
|
/**
|
||||||
* 根路径, 默认是process.cwd()
|
* 根路径, 默认是process.cwd(), type为fileProxy代理有用,必须为绝对路径
|
||||||
*/
|
*/
|
||||||
rootPath?: string;
|
rootPath?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ApiList = {
|
export type ApiList = {
|
||||||
path: string;
|
path: string;
|
||||||
/**
|
/**
|
||||||
|
@ -1,49 +1,103 @@
|
|||||||
import { Server } from 'http';
|
import type { Server as HttpsServer } from 'node:https';
|
||||||
import WebSocket from 'ws';
|
import type { Server as HttpServer } from 'node:http';
|
||||||
|
import type { Http2Server } from 'node:http2';
|
||||||
|
import { WebSocket } from 'ws';
|
||||||
import { ProxyInfo } from './proxy.ts';
|
import { ProxyInfo } from './proxy.ts';
|
||||||
|
import { WebSocketServer } from 'ws';
|
||||||
|
|
||||||
|
export const wss = new WebSocketServer({
|
||||||
|
noServer: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
type WssAppOptions = {
|
||||||
|
wss: WebSocketServer;
|
||||||
|
apiList: ProxyInfo[];
|
||||||
|
};
|
||||||
|
class WssApp {
|
||||||
|
wss: WebSocketServer;
|
||||||
|
apiList: ProxyInfo[];
|
||||||
|
constructor(opts: WssAppOptions) {
|
||||||
|
this.wss = opts.wss;
|
||||||
|
this.apiList = opts.apiList;
|
||||||
|
}
|
||||||
|
upgrade(request: any, socket: any, head: any) {
|
||||||
|
const req = request as any;
|
||||||
|
const wss = this.wss;
|
||||||
|
const url = new URL(req.url, 'http://localhost');
|
||||||
|
const id = url.searchParams.get('id');
|
||||||
|
console.log('upgrade', request.url, id);
|
||||||
|
wss.handleUpgrade(req, socket, head, (ws) => {
|
||||||
|
wss.emit('connection', ws, req);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async handleConnection(ws: WebSocket, req: any) {
|
||||||
|
console.log('connected', req.url);
|
||||||
|
const that = this;
|
||||||
|
const proxyApiList: ProxyInfo[] = that.apiList || [];
|
||||||
|
const proxyApi = proxyApiList.find((item) => req.url.startsWith(item.path));
|
||||||
|
|
||||||
|
if (proxyApi && proxyApi.ws) {
|
||||||
|
const _u = new URL(req.url, `${proxyApi.target}`);
|
||||||
|
if (proxyApi.pathname) {
|
||||||
|
_u.pathname = _u.pathname.replace(proxyApi.path, proxyApi.pathname);
|
||||||
|
}
|
||||||
|
const isHttps = _u.protocol === 'https:';
|
||||||
|
const wsProtocol = isHttps ? 'wss' : 'ws';
|
||||||
|
const wsUrl = `${wsProtocol}://${_u.host}${_u.pathname}`;
|
||||||
|
console.log('WebSocket proxy URL', { wsUrl });
|
||||||
|
const proxySocket = new WebSocket(wsUrl, {
|
||||||
|
// headers: req.headers,
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
});
|
||||||
|
proxySocket.on('open', () => {
|
||||||
|
ws.on('message', (message) => {
|
||||||
|
console.log('WebSocket client message', message);
|
||||||
|
proxySocket.send(message);
|
||||||
|
});
|
||||||
|
proxySocket.on('message', (message) => {
|
||||||
|
// console.log('WebSocket proxy message', message);
|
||||||
|
ws.send(message.toString());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
proxySocket.on('error', (err) => {
|
||||||
|
console.error(`WebSocket proxy error: ${err.message}`);
|
||||||
|
});
|
||||||
|
proxySocket.on('close', () => {
|
||||||
|
console.log('WebSocket proxy closed');
|
||||||
|
});
|
||||||
|
ws.on('error', (err) => {
|
||||||
|
console.error('WebSocket client error', err);
|
||||||
|
proxySocket?.close?.();
|
||||||
|
});
|
||||||
|
ws.on('close', () => {
|
||||||
|
console.log('WebSocket client closed');
|
||||||
|
proxySocket?.close?.();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ws.send(JSON.stringify({ type: 'error', message: 'not found' }));
|
||||||
|
ws.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* websocket代理
|
* websocket代理
|
||||||
* apiList: [{ path: '/api/router', target: 'https://kevisual.xiongxiao.me' }]
|
* apiList: [{ path: '/api/router', target: 'https://kevisual.xiongxiao.me' }]
|
||||||
* @param server
|
* @param server
|
||||||
* @param config
|
* @param config
|
||||||
*/
|
*/
|
||||||
export const wsProxy = (server: Server, config: { apiList: ProxyInfo[] }) => {
|
export const wsProxy = (server: HttpServer | HttpsServer | Http2Server, config: { apiList: ProxyInfo[] }) => {
|
||||||
console.log('Upgrade initialization started');
|
// console.log('Upgrade initialization started');
|
||||||
|
const wssApp = new WssApp({
|
||||||
server.on('upgrade', (req, socket, head) => {
|
wss: wss,
|
||||||
const proxyApiList: ProxyInfo[] = config?.apiList || [];
|
apiList: config.apiList,
|
||||||
const proxyApi = proxyApiList.find((item) => req.url.startsWith(item.path));
|
});
|
||||||
|
wss.on('connection', async (ws, req) => {
|
||||||
if (proxyApi && proxyApi.ws) {
|
// console.log('WebSocket connection established');
|
||||||
const _u = new URL(req.url, `${proxyApi.target}`);
|
await wssApp.handleConnection(ws, req);
|
||||||
const isHttps = _u.protocol === 'https:';
|
});
|
||||||
const wsProtocol = isHttps ? 'wss' : 'ws';
|
// 处理升级请求
|
||||||
const wsUrl = `${wsProtocol}://${_u.hostname}${_u.pathname}`;
|
server.on('upgrade', (request, socket, head) => {
|
||||||
|
wssApp.upgrade(request, socket, head);
|
||||||
const proxySocket = new WebSocket(wsUrl, {
|
|
||||||
headers: req.headers,
|
|
||||||
});
|
|
||||||
|
|
||||||
proxySocket.on('open', () => {
|
|
||||||
socket.on('data', (data) => {
|
|
||||||
proxySocket.send(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
proxySocket.on('message', (message) => {
|
|
||||||
socket.write(message);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
proxySocket.on('error', (err) => {
|
|
||||||
console.error(`WebSocket proxy error: ${err.message}`);
|
|
||||||
socket.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('error', () => {
|
|
||||||
proxySocket.close();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
socket.end();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { program, Command } from 'commander';
|
import { program, Command } from 'commander';
|
||||||
|
import { assistantConfig } from './config.ts';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
// 将多个子命令加入主程序中
|
// 将多个子命令加入主程序中
|
||||||
let version = '0.0.1';
|
let version = '0.0.1';
|
||||||
@ -15,7 +16,7 @@ const ls = new Command('ls').description('List files in the current directory').
|
|||||||
});
|
});
|
||||||
program.addCommand(ls);
|
program.addCommand(ls);
|
||||||
|
|
||||||
export { program, Command };
|
export { program, Command, assistantConfig };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在命令行中运行程序
|
* 在命令行中运行程序
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { app } from './app.ts';
|
import { app } from './app.ts';
|
||||||
import { proxyRoute } from './services/proxy/proxy-page-index.ts';
|
import { proxyRoute, proxyWs } from './services/proxy/proxy-page-index.ts';
|
||||||
|
|
||||||
app.listen(51015, () => {
|
app.listen(51015, () => {
|
||||||
console.log('Server is running on http://localhost:51015');
|
console.log('Server is running on http://localhost:51015');
|
||||||
});
|
});
|
||||||
|
|
||||||
app.server.on(proxyRoute);
|
app.server.on(proxyRoute);
|
||||||
|
|
||||||
|
proxyWs();
|
@ -1,3 +1,4 @@
|
|||||||
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { checkFileExists, AssistantConfig } from '@/module/assistant/index.ts';
|
import { checkFileExists, AssistantConfig } from '@/module/assistant/index.ts';
|
||||||
import { chalk } from '@/module/chalk.ts';
|
import { chalk } from '@/module/chalk.ts';
|
||||||
@ -40,5 +41,10 @@ export class AssistantInit extends AssistantConfig {
|
|||||||
});
|
});
|
||||||
console.log(chalk.green('助手配置文件创建成功'));
|
console.log(chalk.green('助手配置文件创建成功'));
|
||||||
}
|
}
|
||||||
|
const env = this.configPath?.envConfigPath;
|
||||||
|
if (!checkFileExists(env, true)) {
|
||||||
|
fs.writeFileSync(env, '# 环境配置文件\n');
|
||||||
|
console.log(chalk.green('助手环境配置文件创建成功'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,8 +43,7 @@ export class LocalProxy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
init() {
|
init() {
|
||||||
const frontAppDir = this.assistantConfig.configPath?.appDir;
|
const frontAppDir = this.assistantConfig.configPath?.pageDir;
|
||||||
console.log('frontAppDir', frontAppDir);
|
|
||||||
if (frontAppDir) {
|
if (frontAppDir) {
|
||||||
const userList = fs.readdirSync(frontAppDir);
|
const userList = fs.readdirSync(frontAppDir);
|
||||||
const localProxyProxyList: ProxyType[] = [];
|
const localProxyProxyList: ProxyType[] = [];
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { fileProxy, apiProxy, createApiProxy } from '@/module/assistant/index.ts';
|
import { fileProxy, httpProxy, createApiProxy, wsProxy } from '@/module/assistant/index.ts';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
import { LocalProxy } from './local-proxy.ts';
|
import { LocalProxy } from './local-proxy.ts';
|
||||||
import { assistantConfig } from '@/app.ts';
|
import { assistantConfig, app } from '@/app.ts';
|
||||||
import { log } from '@/module/logger.ts';
|
import { log } from '@/module/logger.ts';
|
||||||
const localProxy = new LocalProxy({
|
const localProxy = new LocalProxy({
|
||||||
assistantConfig,
|
assistantConfig,
|
||||||
});
|
});
|
||||||
export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResponse) => {
|
export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||||
const _assistantConfig = assistantConfig.getCacheAssistantConfig();
|
const _assistantConfig = assistantConfig.getCacheAssistantConfig();
|
||||||
const appDir = assistantConfig.configPath?.appDir;
|
const appDir = assistantConfig.configPath?.pageDir;
|
||||||
const url = new URL(req.url, 'http://localhost');
|
const url = new URL(req.url, 'http://localhost');
|
||||||
const pathname = url.pathname;
|
const pathname = url.pathname;
|
||||||
if (pathname.startsWith('/favicon.ico')) {
|
if (pathname.startsWith('/favicon.ico')) {
|
||||||
@ -17,7 +17,7 @@ export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResp
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (pathname.startsWith('/client')) {
|
if (pathname.startsWith('/client')) {
|
||||||
console.log('handle by router');
|
console.debug('handle by router');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// client, api, v1, serve 开头的拦截
|
// client, api, v1, serve 开头的拦截
|
||||||
@ -25,8 +25,8 @@ export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResp
|
|||||||
const defaultApiProxy = createApiProxy(_assistantConfig?.pageApi || 'https://kevisual.cn');
|
const defaultApiProxy = createApiProxy(_assistantConfig?.pageApi || 'https://kevisual.cn');
|
||||||
const apiBackendProxy = [...apiProxyList, ...defaultApiProxy].find((item) => pathname.startsWith(item.path));
|
const apiBackendProxy = [...apiProxyList, ...defaultApiProxy].find((item) => pathname.startsWith(item.path));
|
||||||
if (apiBackendProxy) {
|
if (apiBackendProxy) {
|
||||||
console.log('apiBackendProxy', apiBackendProxy, req.url);
|
log.debug('apiBackendProxy', { apiBackendProxy, url: req.url });
|
||||||
return apiProxy(req, res, {
|
return httpProxy(req, res, {
|
||||||
path: apiBackendProxy.path,
|
path: apiBackendProxy.path,
|
||||||
target: apiBackendProxy.target,
|
target: apiBackendProxy.target,
|
||||||
});
|
});
|
||||||
@ -45,13 +45,19 @@ export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResp
|
|||||||
}
|
}
|
||||||
const proxyApiList = _assistantConfig?.proxy || [];
|
const proxyApiList = _assistantConfig?.proxy || [];
|
||||||
const proxyApi = proxyApiList.find((item) => pathname.startsWith(item.path));
|
const proxyApi = proxyApiList.find((item) => pathname.startsWith(item.path));
|
||||||
if (proxyApi) {
|
if (proxyApi && proxyApi.type === 'file') {
|
||||||
log.log('proxyApi', { proxyApi, pathname });
|
log.debug('proxyApi', { proxyApi, pathname });
|
||||||
const { user, key } = proxyApi;
|
const _indexPath = proxyApi.indexPath || `${_user}/${_app}/index.html`;
|
||||||
|
const _rootPath = proxyApi.rootPath;
|
||||||
|
if (!_rootPath) {
|
||||||
|
log.error('Not Found rootPath', { proxyApi, pathname });
|
||||||
|
return res.end(`Not Found [${proxyApi.path}] rootPath`);
|
||||||
|
}
|
||||||
return fileProxy(req, res, {
|
return fileProxy(req, res, {
|
||||||
path: proxyApi.path, // 代理路径, 比如/root/center
|
path: proxyApi.path, // 代理路径, 比如/root/center
|
||||||
rootPath: appDir, // 根路径
|
rootPath: proxyApi.rootPath,
|
||||||
indexPath: `${user}/${key}/index.html`, // 首页路径
|
...proxyApi,
|
||||||
|
indexPath: _indexPath, // 首页路径
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const localProxyProxyList = localProxy.getLocalProxyList();
|
const localProxyProxyList = localProxy.getLocalProxyList();
|
||||||
@ -60,26 +66,31 @@ export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResp
|
|||||||
log.log('localProxyProxy', { localProxyProxy, url: req.url });
|
log.log('localProxyProxy', { localProxyProxy, url: req.url });
|
||||||
return fileProxy(req, res, {
|
return fileProxy(req, res, {
|
||||||
path: localProxyProxy.path,
|
path: localProxyProxy.path,
|
||||||
rootPath: assistantConfig.configPath?.appDir,
|
rootPath: appDir,
|
||||||
indexPath: localProxyProxy.indexPath,
|
indexPath: localProxyProxy.indexPath,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log('handle by router 404', req.url);
|
|
||||||
const creatCenterProxy = createApiProxy(_assistantConfig?.pageApi || 'https://kevisual.cn', ['/root']);
|
const creatCenterProxy = createApiProxy(_assistantConfig?.pageApi || 'https://kevisual.cn', ['/root']);
|
||||||
const centerProxy = creatCenterProxy.find((item) => pathname.startsWith(item.path));
|
const centerProxy = creatCenterProxy.find((item) => pathname.startsWith(item.path));
|
||||||
if (centerProxy) {
|
if (centerProxy) {
|
||||||
console.log('centerProxy', centerProxy, req.url);
|
return httpProxy(req, res, {
|
||||||
return apiProxy(req, res, {
|
|
||||||
path: centerProxy.path,
|
path: centerProxy.path,
|
||||||
target: centerProxy.target,
|
target: centerProxy.target,
|
||||||
type: 'static',
|
type: 'http',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
log.debug('handle by router 404', req.url);
|
||||||
|
|
||||||
res.statusCode = 404;
|
res.statusCode = 404;
|
||||||
res.end('Not Found Proxy');
|
res.end('Not Found Proxy');
|
||||||
// console.log('getCacheAssistantConfig().pageApi', getCacheAssistantConfig().pageApi);
|
};
|
||||||
// return apiProxy(req, res, {
|
|
||||||
// path: url.pathname,
|
export const proxyWs = () => {
|
||||||
// target: getCacheAssistantConfig().pageApi,
|
const apiProxyList = assistantConfig.getCacheAssistantConfig()?.apiProxyList || [];
|
||||||
// });
|
const proxy = assistantConfig.getCacheAssistantConfig()?.proxy || [];
|
||||||
|
const proxyApi = [...apiProxyList, ...proxy].filter((item) => item.ws);
|
||||||
|
log.debug('proxyApi ', proxyApi);
|
||||||
|
wsProxy(app.server.server, {
|
||||||
|
apiList: proxyApi,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
27
assistant/src/test/ws.ts
Normal file
27
assistant/src/test/ws.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { WebSocket } from 'ws';
|
||||||
|
|
||||||
|
export const main = () => {
|
||||||
|
// https://192.168.31.39:16000/tts
|
||||||
|
const url = 'https://192.168.31.39:16000/tts';
|
||||||
|
// const wss
|
||||||
|
const ws = new WebSocket('wss://192.168.31.39:16000/tts', {
|
||||||
|
rejectUnauthorized: false, // 禁用证书验证
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('open', () => {
|
||||||
|
console.log('WebSocket connection opened');
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('message', (data) => {
|
||||||
|
console.log(`Received message: ${data}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('close', () => {
|
||||||
|
console.log('WebSocket test connection closed');
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('error', (error) => {
|
||||||
|
console.error(`WebSocket error: ${error}`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
main();
|
14
assistant/tasks/silkyai/talkshow.ts
Normal file
14
assistant/tasks/silkyai/talkshow.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
const task1 = {
|
||||||
|
description: '下载前端应用 root/center 应用',
|
||||||
|
command: 'ev app download -i root/center -o assistant-app/page',
|
||||||
|
};
|
||||||
|
|
||||||
|
const task2 = {
|
||||||
|
description: '下载前端应用 root/talkshow-admin 应用',
|
||||||
|
command: 'ev app download -i root/talkshow-admin -o assistant-app/page',
|
||||||
|
};
|
||||||
|
|
||||||
|
const task3 = {
|
||||||
|
description: '安装后端应用 root/talkshow-code-center 应用',
|
||||||
|
command: 'ev app download -i root/talkshow-code-center -t app -o assistant-app/apps/talkshow-code-center',
|
||||||
|
};
|
@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
import { runParser } from '../dist/assistant.mjs';
|
||||||
|
|
||||||
|
runParser(process.argv);
|
1224
pnpm-lock.yaml
generated
1224
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user