Files
cli/assistant/src/module/light-code/index.ts

169 lines
5.8 KiB
TypeScript

import { App, QueryRouterServer } from '@kevisual/router';
import { AssistantInit } from '../../services/init/index.ts';
import path from 'node:path';
import fs, { write } from 'node:fs';
import os from 'node:os';
import glob from 'fast-glob';
import { runCode } from './run.ts';
const codeDemoId = '0e700dc8-90dd-41b7-91dd-336ea51de3d2'
import { filter } from "@kevisual/js-filter";
import { getHash, getStringHash } from '../file-hash.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: AssistantInit
sync?: boolean
}
type LightCodeFile = {
id?: string, code?: string, hash?: string, filepath: string
}
export const initLightCode = async (opts: opts) => {
// 注册 light-code 路由
console.log('初始化 light-code 路由');
const config = opts.config;
const app = opts.router;
const token = config.getConfig()?.token || '';
const query = config.query;
const sync = opts.sync ?? true;
if (!config || !app) {
console.error('initLightCode 缺少必要参数, config 或 app');
return;
}
const appDir = config.configPath.appsDir;
const lightcodeDir = path.join(appDir, 'light-code', 'code');
if (!fs.existsSync(lightcodeDir)) {
fs.mkdirSync(lightcodeDir, { recursive: true });
}
let diffList: LightCodeFile[] = [];
const codeFiles = glob.sync(['**/*.ts', '**/*.js'], {
cwd: lightcodeDir,
onlyFiles: true,
}).map(file => {
return {
filepath: path.join(lightcodeDir, file),
// hash: getHash(path.join(lightcodeDir, file))
}
});
if (sync) {
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 filepath of toDelete) {
fs.unlinkSync(filepath.filepath);
// console.log(`删除 light-code 文件: ${filepath.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
// }));
} else {
console.error('light-code 同步失败', queryRes.message);
diffList = codeFiles;
}
} else {
diffList = codeFiles;
}
for (const file of diffList) {
const tsPath = file.filepath;
const runRes = await runCode(tsPath, { path: 'router', key: 'list' }, { timeout: 10000 });
if (runRes.success) {
const res = runRes.data;
if (res.code === 200) {
const list = res.data?.list || [];
for (const routerItem of list) {
if (routerItem.path?.includes('auth') || routerItem.path?.includes('router') || routerItem.path?.includes('call')) {
continue;
}
// console.log(`注册 light-code 路由: [${routerItem.path}] ${routerItem.id} 来自文件: ${file.filepath}`);
app.route({
id: routerItem.id,
path: routerItem.id!,
description: routerItem.description || '',
metadata: routerItem.metadata || {},
middleware: ['auth'],
}).define(async (ctx) => {
const tokenUser = ctx.state?.tokenUser || {};
const query = { ...ctx.query, tokenUser }
const runRes2 = await runCode(tsPath, query, { 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);
}
}
} else {
console.error('light-code 路由执行失败', runRes.error);
}
}
console.log(`light-code 路由注册成功`, `注册${diffList.length}个路由`);
}