218 lines
7.7 KiB
TypeScript
218 lines
7.7 KiB
TypeScript
import { App, QueryRouterServer } from '@kevisual/router';
|
|
import { AssistantInit } from '../../services/init/index.ts';
|
|
import path from 'node:path';
|
|
import fs from 'node:fs';
|
|
import glob from 'fast-glob';
|
|
import { runCode } from './run.ts';
|
|
const codeDemoId = '0e700dc8-90dd-41b7-91dd-336ea51de3d2'
|
|
import { getHash, getStringHash } from '../file-hash.ts';
|
|
import { AssistantConfig } from '@/lib.ts';
|
|
import { assistantQuery } from '@/app.ts';
|
|
import { logger } from '../logger.ts';
|
|
|
|
const codeDemo = `// 这是一个示例代码文件
|
|
import {App} from '@kevisual/router';
|
|
|
|
const app = new App();
|
|
|
|
app.route({
|
|
path: 'hello',
|
|
description: 'LightCode 示例路由',
|
|
metadata: {
|
|
tags: ['light-code', 'example'],
|
|
},
|
|
}).define(async (ctx) => {
|
|
console.log('tokenUser:', ctx.query?.tokenUser);
|
|
ctx.body = 'Hello from LightCode!';
|
|
}).addTo(app);
|
|
|
|
app.wait();
|
|
`;
|
|
const writeCodeDemo = async (appDir: string) => {
|
|
const lightcodeDir = path.join(appDir, 'light-code', 'code');
|
|
const demoPath = path.join(lightcodeDir, `${codeDemoId}.ts`);
|
|
fs.writeFileSync(demoPath, codeDemo, 'utf-8');
|
|
}
|
|
// writeCodeDemo(path.join(os.homedir(), 'kevisual', 'assistant-app', 'apps'));
|
|
|
|
type Opts = {
|
|
router: QueryRouterServer | App
|
|
config: AssistantConfig | AssistantInit
|
|
sync?: 'remote' | 'local' | 'both'
|
|
rootPath?: string
|
|
}
|
|
type LightCodeFile = {
|
|
id?: string, code?: string, hash?: string, filepath: string
|
|
}
|
|
export const initLightCode = async (opts: Opts) => {
|
|
const token = await assistantQuery.getToken();
|
|
if (!token) {
|
|
logger.error('[light-code] 当前未登录,无法初始化 light-code');
|
|
return;
|
|
}
|
|
// 注册 light-code 路由
|
|
const config = opts.config as AssistantInit;
|
|
const app = opts.router;
|
|
logger.log('[light-code] 初始化 light-code 路由');
|
|
const query = config.query;
|
|
const sync = opts.sync ?? 'remote';
|
|
if (!config || !app) {
|
|
logger.error('[light-code] initLightCode 缺少必要参数, config 或 app');
|
|
return;
|
|
}
|
|
const lightcodeDir = opts.rootPath;
|
|
if (!fs.existsSync(lightcodeDir)) {
|
|
fs.mkdirSync(lightcodeDir, { recursive: true });
|
|
}
|
|
let diffList: LightCodeFile[] = [];
|
|
|
|
const findGlob = (opts: { cwd: string }) => {
|
|
return glob.sync(['**/*.ts', '**/*.js'], {
|
|
cwd: opts.cwd,
|
|
onlyFiles: true,
|
|
}).map(file => {
|
|
return {
|
|
filepath: path.join(opts.cwd, file),
|
|
// hash: getHash(path.join(lightcodeDir, file))
|
|
}
|
|
});
|
|
}
|
|
const codeFiles = findGlob({ cwd: lightcodeDir });
|
|
|
|
if (sync === 'remote' || sync === 'both') {
|
|
const queryRes = await query.post({
|
|
path: 'light-code',
|
|
key: 'list',
|
|
token,
|
|
});
|
|
if (queryRes.code === 200) {
|
|
const lightQueryList = queryRes.data?.list || [];
|
|
for (const item of lightQueryList) {
|
|
const codeHash = getStringHash(item.code || '');
|
|
diffList.push({ id: item.id!, code: item.code || '', hash: codeHash, filepath: path.join(lightcodeDir, `${item.id}.ts`) });
|
|
}
|
|
|
|
|
|
const codeFileSet = new Set(codeFiles.map(f => f.filepath));
|
|
// 需要新增的文件 (在 diffList 中但不在 codeFiles 中)
|
|
const toAdd = diffList.filter(d => !codeFileSet.has(d.filepath));
|
|
// 需要删除的文件 (在 codeFiles 中但不在 diffList 中)
|
|
const toDelete = codeFiles.filter(f => !diffList.some(d => d.filepath === f.filepath));
|
|
// 需要更新的文件 (两边都有但 hash 不同)
|
|
const toUpdate = diffList.filter(d => codeFileSet.has(d.filepath) && d.hash !== getHash(d.filepath));
|
|
const unchanged = diffList.filter(d => codeFileSet.has(d.filepath) && d.hash === getHash(d.filepath));
|
|
|
|
// 执行新增
|
|
for (const item of toAdd) {
|
|
fs.writeFileSync(item.filepath, item.code, 'utf-8');
|
|
// console.log(`新增 light-code 文件: ${item.filepath}`);
|
|
}
|
|
// 执行更新
|
|
for (const item of toUpdate) {
|
|
fs.writeFileSync(item.filepath, item.code, 'utf-8');
|
|
// console.log(`更新 light-code 文件: ${item.filepath}`);
|
|
}
|
|
// 记录未更新的文件
|
|
// const lightCodeList = [...toAdd, ...unchanged].map(d => ({
|
|
// filepath: d.filepath,
|
|
// hash: d.hash
|
|
// }));
|
|
|
|
if (sync === 'remote') {
|
|
// 执行删除
|
|
for (const filepath of toDelete) {
|
|
// console.log(`删除 light-code 文件: ${filepath.filepath}`);
|
|
const parentDir = path.dirname(filepath.filepath);
|
|
// console.log('parentDir', parentDir, lightcodeDir);
|
|
if (parentDir === lightcodeDir) {
|
|
fs.unlinkSync(filepath.filepath);
|
|
}
|
|
}
|
|
}
|
|
diffList = findGlob({ cwd: lightcodeDir });
|
|
} else {
|
|
console.error('[light-code] 同步失败', queryRes.message);
|
|
diffList = codeFiles;
|
|
}
|
|
} else if (sync === 'local') {
|
|
diffList = codeFiles;
|
|
}
|
|
|
|
|
|
for (const file of diffList) {
|
|
const tsPath = file.filepath;
|
|
const runRes = await runCode(tsPath, { message: { path: 'router', key: 'list' } }, { timeout: 10000 });
|
|
// console.log('light-code 运行结果', file.filepath, runRes);
|
|
if (runRes.success) {
|
|
const res = runRes.data;
|
|
if (res.code === 200) {
|
|
const list = res.data?.list || [];
|
|
for (const routerItem of list) {
|
|
// console.log('注册 light-code 路由项:', routerItem.id, routerItem.path);
|
|
if (routerItem.path?.includes('auth') || routerItem.path?.includes('router') || routerItem.path?.includes('call')) {
|
|
continue;
|
|
}
|
|
// console.log(`注册 light-code 路由: [${routerItem.path}] ${routerItem.id} 来自文件: ${file.filepath}`);
|
|
const metadata = routerItem.metadata || {};
|
|
if (metadata.tags && Array.isArray(metadata.tags)) {
|
|
metadata.tags.push('light-code');
|
|
} else {
|
|
metadata.tags = ['light-code'];
|
|
}
|
|
metadata.source = 'light-code';
|
|
metadata['light-code'] = {
|
|
id: file.id
|
|
};
|
|
(app as App).route({
|
|
id: routerItem.id,
|
|
path: `${routerItem.id}__${routerItem.path}`,
|
|
key: routerItem.key,
|
|
description: routerItem.description || '',
|
|
metadata,
|
|
middleware: ['auth'],
|
|
}).define(async (ctx) => {
|
|
const tokenUser = ctx.state?.tokenUser || {};
|
|
const query = { ...ctx.query }
|
|
const runRes2 = await runCode(tsPath, {
|
|
message: query,
|
|
context: {
|
|
state: { tokenUser, user: tokenUser },
|
|
}
|
|
}, { timeout: 30000 });
|
|
if (runRes2.success) {
|
|
const res2 = runRes2.data;
|
|
if (res2.code === 200) {
|
|
ctx.body = res2.data;
|
|
} else {
|
|
ctx.throw(res2.code, res2.message || 'Lightcode 路由执行失败');
|
|
}
|
|
} else {
|
|
ctx.throw(runRes2.error || 'Lightcode 路由执行失败');
|
|
}
|
|
}).addTo(app, {
|
|
overwrite: false
|
|
});// 不允许覆盖已存在的路由
|
|
// console.log(`light-code 路由注册成功: [${routerItem.path}] ${routerItem.id} 来自文件: ${file.filepath}`);
|
|
}
|
|
}
|
|
} else {
|
|
console.error('[light-code] 路由执行失败', runRes.error);
|
|
}
|
|
}
|
|
console.log(`[light-code] 路由注册成功`, `注册${diffList.length}个路由`);
|
|
}
|
|
|
|
export const clearLightCodeRoutes = (opts: Pick<Opts, 'router'>) => {
|
|
const app = opts.router;
|
|
if (!app) {
|
|
console.error('[light-code] clearLightCodeRoutes 缺少必要参数, app');
|
|
return;
|
|
}
|
|
const routes = app.getList();
|
|
for (const route of routes) {
|
|
if (route.metadata?.source === 'light-code') {
|
|
// console.log(`[light-code] 删除 light-code 路由: ${route.path} ${route.id}`);
|
|
app.removeById(route.id);
|
|
}
|
|
}
|
|
} |