feat: add dev module
This commit is contained in:
parent
8b136d1a5a
commit
a1344b9220
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,3 +6,4 @@ coverage
|
||||
.env
|
||||
/logs
|
||||
|
||||
cache
|
58
package.json
58
package.json
@ -1,30 +1,62 @@
|
||||
{
|
||||
"name": "dev-app",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.2",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"app": {
|
||||
"type": "micro-app"
|
||||
"type": "system-app",
|
||||
"entry": "dist/app.mjs",
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"watch": "cross-env NODE_ENV=development rollup -c -w --watch src",
|
||||
"build": "rollup -c",
|
||||
"dev": "concurrently \"pnpm watch\" \"nodemon --watch dist dist/app.mjs\""
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"keywords": [],
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me>",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@9.8.0+sha512.8e4c3550fb500e808dbc30bb0ce4dd1eb614e30b1c55245f211591ec2cdf9c611cabd34e1364b42f564bd54b3945ed0f49d61d1bbf2ec9bd74b866fcdc723276",
|
||||
"devDependencies": {
|
||||
"@kevisual/router": "0.0.6-alpha-2",
|
||||
"@kevisual/types": "^0.0.1",
|
||||
"@kevisual/use-config": "^1.0.4",
|
||||
"@types/node": "^22.10.1",
|
||||
"typescript": "^5.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "^22.10.1",
|
||||
"concurrently": "^9.1.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"fast-glob": "^3.3.2",
|
||||
"nodemon": "^3.1.7",
|
||||
"@kevisual/query": "0.0.7-alpha.3",
|
||||
"@kevisual/router": "0.0.6-alpha-2",
|
||||
"@kevisual/types": "^0.0.2",
|
||||
"@kevisual/use-config": "^1.0.5",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"@rollup/plugin-alias": "^5.1.1",
|
||||
"@rollup/plugin-commonjs": "^28.0.1",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.3.0",
|
||||
"@rollup/plugin-replace": "^6.0.1",
|
||||
"@rollup/plugin-typescript": "^12.1.1",
|
||||
"esbuild": "^0.24.0",
|
||||
"nanoid": "^5.0.9",
|
||||
"sequelize": "^6.37.5",
|
||||
"sqlite3": "^5.1.7"
|
||||
}
|
||||
"sqlite3": "^5.1.7",
|
||||
"rollup": "^4.28.0",
|
||||
"rollup-plugin-copy": "^3.5.0",
|
||||
"rollup-plugin-dts": "^6.1.1",
|
||||
"rollup-plugin-esbuild": "^6.1.1",
|
||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"tailwind-merge": "^2.5.5",
|
||||
"tailwindcss": "^3.4.16",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript-eslint": "^8.17.0",
|
||||
"vite": "^6.0.3"
|
||||
},
|
||||
"packageManager": "pnpm@9.14.4"
|
||||
}
|
2795
pnpm-lock.yaml
generated
2795
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
118
rollup.config.mjs
Normal file
118
rollup.config.mjs
Normal file
@ -0,0 +1,118 @@
|
||||
// @ts-check
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import copy from 'rollup-plugin-copy';
|
||||
import { dts } from 'rollup-plugin-dts';
|
||||
import json from '@rollup/plugin-json';
|
||||
import * as glob from 'fast-glob';
|
||||
import path from 'path';
|
||||
import esbuild from 'rollup-plugin-esbuild';
|
||||
import alias from '@rollup/plugin-alias';
|
||||
import replace from '@rollup/plugin-replace';
|
||||
import fs from 'fs';
|
||||
const pkgs = JSON.parse(fs.readFileSync(new URL('./package.json', import.meta.url), 'utf-8'));
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
/**
|
||||
* @type {import('rollup').RollupOptions}
|
||||
*/
|
||||
const config = {
|
||||
input: './src/index.ts',
|
||||
// input: './src/micro-client.ts',
|
||||
output: {
|
||||
dir: './dist',
|
||||
entryFileNames: 'app.mjs',
|
||||
chunkFileNames: '[name]-[hash].mjs',
|
||||
format: 'esm',
|
||||
},
|
||||
plugins: [
|
||||
alias({
|
||||
// only esbuild needs to be configured
|
||||
entries: [
|
||||
{ find: '@', replacement: path.resolve('src') }, // 配置 @ 为 src 目录
|
||||
{ find: 'http', replacement: 'node:http' },
|
||||
{ find: 'https', replacement: 'node:https' },
|
||||
{ find: 'fs', replacement: 'node:fs' },
|
||||
{ find: 'path', replacement: 'node:path' },
|
||||
{ find: 'crypto', replacement: 'node:crypto' },
|
||||
{ find: 'zlib', replacement: 'node:zlib' },
|
||||
{ find: 'stream', replacement: 'node:stream' },
|
||||
{ find: 'net', replacement: 'node:net' },
|
||||
{ find: 'tty', replacement: 'node:tty' },
|
||||
{ find: 'tls', replacement: 'node:tls' },
|
||||
{ find: 'buffer', replacement: 'node:buffer' },
|
||||
{ find: 'timers', replacement: 'node:timers' },
|
||||
// { find: 'string_decoder', replacement: 'node:string_decoder' },
|
||||
{ find: 'dns', replacement: 'node:dns' },
|
||||
{ find: 'domain', replacement: 'node:domain' },
|
||||
{ find: 'os', replacement: 'node:os' },
|
||||
{ find: 'events', replacement: 'node:events' },
|
||||
{ find: 'url', replacement: 'node:url' },
|
||||
{ find: 'assert', replacement: 'node:assert' },
|
||||
{ find: 'util', replacement: 'node:util' },
|
||||
],
|
||||
}),
|
||||
replace({
|
||||
preventAssignment: true, // 避免直接赋值
|
||||
DEV_SERVER: JSON.stringify(isDev), // 替换 DEV_SERVER
|
||||
}),
|
||||
resolve({
|
||||
preferBuiltins: true, // 强制优先使用内置模块
|
||||
}),
|
||||
commonjs(),
|
||||
esbuild({
|
||||
target: 'node22', // 目标为 Node.js 14
|
||||
minify: false, // 启用代码压缩
|
||||
tsconfig: 'tsconfig.json',
|
||||
}),
|
||||
json(),
|
||||
],
|
||||
external: [
|
||||
'sequelize', //
|
||||
'pg',
|
||||
'@kevisual/router',
|
||||
'ioredis',
|
||||
'socket.io',
|
||||
'minio',
|
||||
'pino',
|
||||
'@msgpack/msgpack',
|
||||
'esbuild',
|
||||
],
|
||||
};
|
||||
const configs = [
|
||||
{
|
||||
input: './src/scripts/init-data.ts',
|
||||
output: {
|
||||
dir: './dist',
|
||||
entryFileNames: 'init-data.mjs',
|
||||
format: 'esm',
|
||||
},
|
||||
plugins: [
|
||||
alias({
|
||||
// only esbuild needs to be configured
|
||||
entries: [
|
||||
{ find: '@', replacement: path.resolve('src') }, // 配置 @ 为 src 目录
|
||||
],
|
||||
}),
|
||||
resolve(),
|
||||
commonjs(),
|
||||
esbuild({
|
||||
target: 'node22',
|
||||
minify: false,
|
||||
tsconfig: 'tsconfig.json',
|
||||
}),
|
||||
],
|
||||
external: [
|
||||
'sequelize', //
|
||||
'pg',
|
||||
'@kevisual/router',
|
||||
'ioredis',
|
||||
'socket.io',
|
||||
'minio',
|
||||
'pino',
|
||||
'@msgpack/msgpack',
|
||||
],
|
||||
},
|
||||
];
|
||||
export default [...configs, config];
|
13
simple/vite-demo/index.html
Normal file
13
simple/vite-demo/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite Demo</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome to Vite Demo</h1>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="./src/index.ts"></script>
|
||||
</body>
|
||||
</html>
|
1
simple/vite-demo/src/index.ts
Normal file
1
simple/vite-demo/src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
document.querySelector('#app')!.innerHTML = 'Hello Vite!'
|
@ -1,5 +1,5 @@
|
||||
import { App } from '@kevisual/router';
|
||||
|
||||
import { DevManater } from './modules/manager.ts';
|
||||
export const app = new App();
|
||||
|
||||
app.listen(9998)
|
||||
export const devManager = new DevManater();
|
||||
|
26
src/index.ts
26
src/index.ts
@ -1,11 +1,23 @@
|
||||
import { App } from '@kevisual/router';
|
||||
import { app } from './app.ts';
|
||||
import './route/ssh/list.ts';
|
||||
import './route/dev/index.ts';
|
||||
|
||||
app
|
||||
.call({
|
||||
path: 'ssh',
|
||||
key: 'list',
|
||||
})
|
||||
.then((res) => {
|
||||
console.log(res);
|
||||
if (DEV_SERVER) {
|
||||
// app
|
||||
// .call({
|
||||
// path: 'ssh',
|
||||
// key: 'list',
|
||||
// })
|
||||
// .then((res) => {
|
||||
// console.log(res);
|
||||
// });
|
||||
|
||||
app.listen(9998, () => {
|
||||
console.log(`server is running on http://localhost:9998`);
|
||||
});
|
||||
}
|
||||
|
||||
export const loadApp = (mainApp: App) => {
|
||||
mainApp.importApp(app);
|
||||
};
|
||||
|
0
src/lib/build/index.ts
Normal file
0
src/lib/build/index.ts
Normal file
41
src/modules/build/convert.ts
Normal file
41
src/modules/build/convert.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { build } from 'esbuild';
|
||||
import { resolve } from 'path';
|
||||
import { cacheFilePath } from '../config.ts';
|
||||
|
||||
export type Opts = {
|
||||
inputCode: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* 转换代码为 MJS
|
||||
* @param opts
|
||||
* @returns
|
||||
*/
|
||||
export async function transformToMJS(opts: Opts) {
|
||||
const { inputCode } = opts;
|
||||
const outputPath = resolve(cacheFilePath, `${opts.id}.mjs`);
|
||||
try {
|
||||
// 1. 使用 esbuild 转换代码
|
||||
const result = await build({
|
||||
stdin: {
|
||||
contents: inputCode,
|
||||
resolveDir: process.cwd(), // 当前目录为基准路径,用于解析文件路径
|
||||
loader: 'ts',
|
||||
},
|
||||
format: 'esm', // 输出为 ESM 格式
|
||||
outfile: outputPath, // 输出文件路径
|
||||
// bundle: true, // 启用打包
|
||||
platform: 'node', // 指定平台为 node
|
||||
// external: ['node_modules/*'], // 排除所有 node_modules 下的依赖
|
||||
});
|
||||
console.log(`转换完成,文件已保存到: ${outputPath}`);
|
||||
return { code: 200, outputPath };
|
||||
} catch (e) {
|
||||
console.error('transformToMJS error', e);
|
||||
return {
|
||||
code: 500,
|
||||
message: e.message,
|
||||
};
|
||||
}
|
||||
}
|
1
src/modules/build/index.ts
Normal file
1
src/modules/build/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './convert.ts';
|
3
src/modules/config.ts
Normal file
3
src/modules/config.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { useFileStore } from '@kevisual/use-config/file-store';
|
||||
|
||||
export const cacheFilePath = useFileStore('cache', { needExists: true });
|
159
src/modules/manager.ts
Normal file
159
src/modules/manager.ts
Normal file
@ -0,0 +1,159 @@
|
||||
import { spawn } from 'child_process';
|
||||
import { DevData, DevModel } from '../route/dev/model.ts';
|
||||
import { fileIsExist } from '@kevisual/use-config';
|
||||
import { transformToMJS } from './build/convert.ts';
|
||||
|
||||
/**
|
||||
* 创建代码路径
|
||||
* @param dev
|
||||
*/
|
||||
export const createCodePath = async (dev: DevModel) => {
|
||||
const data = dev.data;
|
||||
let { type, code } = data;
|
||||
if (type === 'script' && code) {
|
||||
const res = await transformToMJS({ inputCode: code, id: dev.id });
|
||||
if (res.code !== 200) {
|
||||
data.codePath = '';
|
||||
data.codeStatus = 'error';
|
||||
await dev.update({ data }, { logging: false, fields: ['data'] });
|
||||
throw new Error('transformToMJS error:' + res.message);
|
||||
}
|
||||
data.codePath = res.outputPath;
|
||||
data.codeStatus = 'success';
|
||||
data.status = 'active';
|
||||
await DevModel.update({ data: { ...data } }, { where: { id: dev.id }, logging: false });
|
||||
}
|
||||
};
|
||||
type Dev = {
|
||||
id: string;
|
||||
key: string;
|
||||
pid: number; // 进程id
|
||||
title: string;
|
||||
description: string;
|
||||
} & DevData;
|
||||
// 检察app数据
|
||||
export const checkAppData = async (dev: DevModel) => {
|
||||
const data = dev.data;
|
||||
let { type, codePath, cwd } = data;
|
||||
if (type === 'script') {
|
||||
let checkFileIsExist = false;
|
||||
if (codePath) {
|
||||
checkFileIsExist = fileIsExist(codePath);
|
||||
}
|
||||
if (!codePath || !checkFileIsExist) {
|
||||
await createCodePath(dev);
|
||||
}
|
||||
if (!cwd) {
|
||||
throw new Error('cwd is required');
|
||||
}
|
||||
} else if (type === 'shell') {
|
||||
if (!cwd) {
|
||||
throw new Error('cwd is required');
|
||||
}
|
||||
const { command, path } = data;
|
||||
if (!command) {
|
||||
throw new Error('command is required');
|
||||
}
|
||||
if (!path) {
|
||||
throw new Error('path is required');
|
||||
}
|
||||
}
|
||||
};
|
||||
export const runApp = async (dev: Dev) => {
|
||||
let { type, command, env, cwd, codePath, path } = dev;
|
||||
let pid = 0;
|
||||
if (type === 'script') {
|
||||
const scriptCommand = command || 'node';
|
||||
const params = [];
|
||||
if (scriptCommand === 'vite' || scriptCommand === 'rollup') {
|
||||
params.push('-c', codePath);
|
||||
}
|
||||
// node启动
|
||||
const childProcess = spawn(command, params, {
|
||||
env: {
|
||||
...process.env,
|
||||
...env,
|
||||
},
|
||||
shell: true,
|
||||
stdio: 'inherit',
|
||||
cwd,
|
||||
});
|
||||
childProcess.stdout?.on('data', (data) => {
|
||||
console.log(dev.id, 'stdout', data.toString());
|
||||
});
|
||||
childProcess.stderr?.on('data', (data) => {
|
||||
console.log(dev.id, 'stderr', data.toString());
|
||||
});
|
||||
console.log('child pid ', childProcess.pid);
|
||||
pid = childProcess.pid;
|
||||
} else if (type === 'shell') {
|
||||
// shell启动
|
||||
const childProcess = spawn(command, [path], {
|
||||
env: {
|
||||
...process.env,
|
||||
...env,
|
||||
},
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
cwd,
|
||||
});
|
||||
pid = childProcess.pid;
|
||||
}
|
||||
dev.pid = pid;
|
||||
};
|
||||
/**
|
||||
* check pid is exist
|
||||
* @param pid
|
||||
* @returns
|
||||
*/
|
||||
export const checkPid = (pid: number) => {
|
||||
try {
|
||||
process.kill(pid, 0);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
export const stopApp = async (dev: Dev) => {
|
||||
const { pid } = dev;
|
||||
if (pid) {
|
||||
if (checkPid(pid)) {
|
||||
console.log('stopApp', dev.id, 'pid', pid);
|
||||
process.kill(pid);
|
||||
}
|
||||
}
|
||||
};
|
||||
export class DevManater {
|
||||
devList: Dev[] = [];
|
||||
async create(dev: DevModel) {
|
||||
const data = dev.data;
|
||||
await checkAppData(dev);
|
||||
let hasDev = this.devList.find((item) => item.id === dev.id || item.key === dev.key);
|
||||
if (hasDev) {
|
||||
await stopApp(hasDev);
|
||||
this.devList = this.devList.filter((item) => item.id !== dev.id && item.key !== dev.key);
|
||||
}
|
||||
const _dev = {
|
||||
id: dev.id,
|
||||
key: dev.key,
|
||||
title: dev.title,
|
||||
description: dev.description,
|
||||
pid: 0,
|
||||
...data,
|
||||
};
|
||||
await runApp(_dev);
|
||||
this.devList.push(_dev);
|
||||
return _dev;
|
||||
}
|
||||
async stop(id: string) {
|
||||
const dev = this.devList.find((item) => item.id === id);
|
||||
if (dev) {
|
||||
await stopApp(dev);
|
||||
this.devList = this.devList.filter((item) => item.id !== id);
|
||||
}
|
||||
return dev;
|
||||
}
|
||||
async getList() {
|
||||
return this.devList;
|
||||
}
|
||||
}
|
5
src/modules/md5.ts
Normal file
5
src/modules/md5.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { createHash } from 'crypto';
|
||||
|
||||
export function md5(input: string): string {
|
||||
return createHash('md5').update(input).digest('hex');
|
||||
}
|
7
src/modules/memory.ts
Normal file
7
src/modules/memory.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { Sequelize } from 'sequelize';
|
||||
|
||||
export const memory = new Sequelize({
|
||||
dialect: 'sqlite',
|
||||
storage: ':memory:',
|
||||
logging: false,
|
||||
});
|
105
src/route/dev/dev-list.ts
Normal file
105
src/route/dev/dev-list.ts
Normal file
@ -0,0 +1,105 @@
|
||||
// 获取文件的
|
||||
import { app } from '@/app.ts';
|
||||
import { DevModel } from './model.ts';
|
||||
import { md5 } from '@/modules/md5.ts';
|
||||
import { createCodePath } from '@/modules/manager.ts';
|
||||
|
||||
app
|
||||
.route({
|
||||
path: 'dev-app',
|
||||
key: 'list',
|
||||
description: '获取开发配置列表',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const list = await DevModel.findAll({
|
||||
logging: false,
|
||||
});
|
||||
ctx.body = list;
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
app
|
||||
.route({
|
||||
path: 'dev-app',
|
||||
key: 'update',
|
||||
description: '更新开发配置',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const data = ctx.query.data || {};
|
||||
const id = data.id;
|
||||
let dev: DevModel;
|
||||
let hashChange = false || !!ctx.query.force;
|
||||
if (id) {
|
||||
dev = await DevModel.findByPk(id);
|
||||
if (!dev) {
|
||||
ctx.throw(404, 'dev not found');
|
||||
return;
|
||||
}
|
||||
const updateData = {
|
||||
...dev.data,
|
||||
...data?.data,
|
||||
};
|
||||
console.log('updateData', updateData);
|
||||
if (updateData?.code) {
|
||||
const codeHash = md5(updateData.code);
|
||||
if (codeHash !== dev.data.codeHash) {
|
||||
updateData.codeHash = codeHash;
|
||||
updateData.codePath = '';
|
||||
hashChange = true;
|
||||
}
|
||||
}
|
||||
await dev.update(
|
||||
{
|
||||
title: dev.title,
|
||||
description: dev.description,
|
||||
data: updateData,
|
||||
},
|
||||
{
|
||||
logging: false,
|
||||
fields: ['title', 'key', 'description', 'data'],
|
||||
},
|
||||
);
|
||||
} else {
|
||||
const updateData = {
|
||||
...data?.data,
|
||||
};
|
||||
if (updateData?.code) {
|
||||
const codeHash = md5(updateData.code);
|
||||
updateData.codeHash = codeHash;
|
||||
updateData.codePath = '';
|
||||
hashChange = true;
|
||||
}
|
||||
dev = await DevModel.create({
|
||||
...updateData,
|
||||
});
|
||||
}
|
||||
if (hashChange && dev.data?.status === 'active') {
|
||||
await createCodePath(dev);
|
||||
}
|
||||
ctx.body = dev;
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
app
|
||||
.route({
|
||||
path: 'dev-app',
|
||||
key: 'delete',
|
||||
description: '删除开发配置',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const id = ctx.query.id;
|
||||
if (!id) {
|
||||
ctx.throw(400, 'id is required');
|
||||
return;
|
||||
}
|
||||
const dev = await DevModel.findByPk(id);
|
||||
if (!dev) {
|
||||
ctx.throw(404, 'dev not found');
|
||||
return;
|
||||
}
|
||||
await dev.destroy({
|
||||
logging: false,
|
||||
});
|
||||
ctx.body = 'ok';
|
||||
})
|
||||
.addTo(app);
|
2
src/route/dev/index.ts
Normal file
2
src/route/dev/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import './dev-list.ts';
|
||||
import './operate.ts';
|
@ -1,15 +1,18 @@
|
||||
import { DataTypes, Model, Op } from 'sequelize';
|
||||
import { sequelize } from '@/modules/sequelize.ts';
|
||||
|
||||
type DevData = {
|
||||
export type DevData = {
|
||||
cwd?: string; // 当前工作目录
|
||||
command: string; // 启动命令
|
||||
type: 'node' | 'shell' | 'script'; // 启动类型
|
||||
type: 'shell' | 'script'; // 启动类型
|
||||
env?: Record<string, string>; // 环境变量
|
||||
keepalive?: boolean; // 是否保持运行, 让用户知道是不是保持运行的
|
||||
code?: string; // 代码
|
||||
status?: 'active' | 'inactive'; // 状态
|
||||
path?: string; // esbuild 打包路径,用于 node 启动
|
||||
codeHash?: string; // 代码hash
|
||||
codePath?: string; // 代码路径, 转成 code 的路径
|
||||
codeStatus?: 'success' | 'error'; // 转换状态
|
||||
status: 'active' | 'inactive'; // 状态, 是否生成codePath
|
||||
path?: string; // esbuild 打包路径,用于 node 启动, vite 配置 esbuild 配置,rollup配置路径等
|
||||
};
|
||||
export type Dev = Partial<InstanceType<typeof DevModel>>;
|
||||
|
||||
@ -19,6 +22,7 @@ export type Dev = Partial<InstanceType<typeof DevModel>>;
|
||||
export class DevModel extends Model {
|
||||
declare id: string;
|
||||
declare title: string;
|
||||
declare key: string;
|
||||
declare description: string;
|
||||
declare data: DevData;
|
||||
}
|
||||
@ -34,6 +38,11 @@ DevModel.init(
|
||||
allowNull: false,
|
||||
defaultValue: '',
|
||||
},
|
||||
key: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
defaultValue: '',
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
@ -48,13 +57,12 @@ DevModel.init(
|
||||
sequelize,
|
||||
paranoid: true,
|
||||
modelName: 'local_dev',
|
||||
freezeTableName: true, // 禁用表名复数化
|
||||
},
|
||||
);
|
||||
|
||||
DevModel.sync({
|
||||
await DevModel.sync({
|
||||
alter: true,
|
||||
logging: false,
|
||||
}).catch((e) => {
|
||||
console.error(e);
|
||||
console.error('DevModel sync error', e);
|
||||
});
|
||||
|
115
src/route/dev/operate.ts
Normal file
115
src/route/dev/operate.ts
Normal file
@ -0,0 +1,115 @@
|
||||
import { app, devManager } from '@/app.ts';
|
||||
import { DevModel } from './model.ts';
|
||||
import { transformToMJS } from '@/modules/build/index.ts';
|
||||
|
||||
app
|
||||
.route({
|
||||
path: 'dev-app',
|
||||
key: 'createConfig',
|
||||
description: '创建开发的配置项,比如vite的执行配置。',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const id = ctx.query.id;
|
||||
if (!id) {
|
||||
ctx.throw(400, 'id is required');
|
||||
return;
|
||||
}
|
||||
const dev = await DevModel.findByPk(id);
|
||||
if (!dev) {
|
||||
ctx.throw(404, 'dev not found');
|
||||
return;
|
||||
}
|
||||
const data = dev.data;
|
||||
if (!data) {
|
||||
ctx.throw(400, 'data is required');
|
||||
return;
|
||||
}
|
||||
const { type, path, code, codePath, command, env } = data;
|
||||
if (type !== 'script') {
|
||||
ctx.throw(400, 'type is not script');
|
||||
return;
|
||||
}
|
||||
const res = await transformToMJS({ inputCode: code, id: id });
|
||||
if (res.code !== 200) {
|
||||
ctx.throw(res.code, res.message);
|
||||
return;
|
||||
}
|
||||
await dev.update(
|
||||
{
|
||||
data: {
|
||||
...data,
|
||||
codePath: res.outputPath,
|
||||
codeStatus: 'success',
|
||||
},
|
||||
},
|
||||
{
|
||||
logging: false,
|
||||
fields: ['data'],
|
||||
},
|
||||
);
|
||||
ctx.body = dev;
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
app
|
||||
.route({
|
||||
path: 'dev-app',
|
||||
key: 'runningList',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const list = await devManager.getList();
|
||||
ctx.body = list;
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
app
|
||||
.route({
|
||||
path: 'dev-app',
|
||||
key: 'run',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const id = ctx.query.id;
|
||||
const key = ctx.query.appKey;
|
||||
if (!id && !key) {
|
||||
ctx.throw(400, 'id or appKey is required');
|
||||
return;
|
||||
}
|
||||
let dev: DevModel;
|
||||
if (id) {
|
||||
dev = await DevModel.findByPk(id, {
|
||||
logging: false,
|
||||
});
|
||||
}
|
||||
if (!dev) {
|
||||
dev = await DevModel.findOne({
|
||||
where: {
|
||||
key,
|
||||
},
|
||||
logging: false,
|
||||
});
|
||||
}
|
||||
if (!dev) {
|
||||
ctx.throw(404, 'dev not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const devInfo = await devManager.create(dev);
|
||||
ctx.body = devInfo;
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
app
|
||||
.route({
|
||||
path: 'dev-app',
|
||||
key: 'stop',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const id = ctx.query.id;
|
||||
if (!id) {
|
||||
ctx.throw(400, 'id is required');
|
||||
return;
|
||||
}
|
||||
const dev = await devManager.stop(id);
|
||||
ctx.body = dev;
|
||||
})
|
||||
.addTo(app);
|
@ -9,7 +9,11 @@ const init = async () => {
|
||||
sshManager.createServer(ssh.remote, ssh.configs);
|
||||
});
|
||||
};
|
||||
try {
|
||||
if (!DEV_SERVER) {
|
||||
setTimeout(init, 1000);
|
||||
}
|
||||
} catch (e) {}
|
||||
app
|
||||
.route({
|
||||
path: 'ssh',
|
||||
|
@ -11,7 +11,6 @@ export type SSH = Partial<InstanceType<typeof SSHModel>>;
|
||||
|
||||
export class SSHModel extends Model {
|
||||
declare id: string;
|
||||
// declare title: string;
|
||||
declare description: string;
|
||||
declare configs: SSHModelData[];
|
||||
declare remote: string;
|
||||
@ -23,11 +22,6 @@ SSHModel.init(
|
||||
primaryKey: true,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
},
|
||||
// title: {
|
||||
// type: DataTypes.TEXT,
|
||||
// allowNull: false,
|
||||
// defaultValue: '',
|
||||
// },
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
@ -47,11 +41,10 @@ SSHModel.init(
|
||||
sequelize,
|
||||
paranoid: true,
|
||||
modelName: 'local_ssh',
|
||||
freezeTableName: true, // 禁用表名复数化
|
||||
},
|
||||
);
|
||||
|
||||
SSHModel.sync({
|
||||
await SSHModel.sync({
|
||||
alter: true,
|
||||
logging: false,
|
||||
}).catch((e) => {
|
||||
|
9
src/scripts-config/configs/vite-demo.ts
Normal file
9
src/scripts-config/configs/vite-demo.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
root: '/Users/xion/kevisual/dev-app/simple/vite-demo',
|
||||
plugins: [],
|
||||
define: {
|
||||
DEV_SERVER: JSON.stringify(process.env.NODE_ENV === 'development'),
|
||||
},
|
||||
});
|
79
src/scripts-config/vite-demo.ts
Normal file
79
src/scripts-config/vite-demo.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { Query } from '@kevisual/query/query';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const __dirname = path.dirname(new URL(import.meta.url).pathname);
|
||||
const query = new Query({
|
||||
url: 'http://localhost:9998/api/router',
|
||||
});
|
||||
|
||||
const getList = async () => {
|
||||
const res = await query.post({
|
||||
path: 'dev-app',
|
||||
key: 'list',
|
||||
});
|
||||
const list = res.data;
|
||||
console.log(JSON.stringify(list, null, 2));
|
||||
};
|
||||
// await getList();
|
||||
|
||||
const addOne = async () => {
|
||||
const res = await query.post({
|
||||
path: 'dev-app',
|
||||
key: 'update',
|
||||
data: {
|
||||
title: 'vite test',
|
||||
description: 'test',
|
||||
// key: 'vite-demo',
|
||||
data: {
|
||||
code: fs.readFileSync(path.resolve(__dirname, './configs/vite-demo.ts'), 'utf-8'),
|
||||
type: 'script',
|
||||
cwd: '/Users/xion/kevisual/dev-app/simple/vite-demo',
|
||||
status: 'active',
|
||||
command: 'vite',
|
||||
},
|
||||
},
|
||||
});
|
||||
console.log(res);
|
||||
};
|
||||
|
||||
// await addOne();
|
||||
|
||||
const updateOne = async () => {
|
||||
const updateData = {
|
||||
id: '6e83c9b1-f16d-4a67-ac53-480aaafcf4fb',
|
||||
data: {
|
||||
code: fs.readFileSync(path.resolve(__dirname, './configs/vite-demo.ts'), 'utf-8'),
|
||||
type: 'script',
|
||||
cwd: '/Users/xion/kevisual/dev-app/simple/vite-demo',
|
||||
status: 'active',
|
||||
command: 'vite',
|
||||
},
|
||||
};
|
||||
const res = await query.post({
|
||||
path: 'dev-app',
|
||||
key: 'update',
|
||||
data: updateData,
|
||||
force: true,
|
||||
});
|
||||
console.log(res);
|
||||
};
|
||||
// await updateOne();
|
||||
const runOne = async (id: string) => {
|
||||
const res = await query.post({
|
||||
path: 'dev-app',
|
||||
key: 'run',
|
||||
id: id,
|
||||
});
|
||||
console.log(res);
|
||||
};
|
||||
await runOne('6e83c9b1-f16d-4a67-ac53-480aaafcf4fb');
|
||||
|
||||
const deleteOne = async (id: string) => {
|
||||
const res = await query.post({
|
||||
path: 'dev-app',
|
||||
key: 'delete',
|
||||
id,
|
||||
});
|
||||
console.log(res);
|
||||
};
|
@ -1,70 +1,16 @@
|
||||
import { app } from '../app.ts';
|
||||
import '../index.ts';
|
||||
const sleep = (time: number) => new Promise((resolve) => setTimeout(resolve, time));
|
||||
import { data } from './init-data.ts';
|
||||
const main = async () => {
|
||||
await sleep(3000);
|
||||
// console.log('add nisar')
|
||||
|
||||
app.call({
|
||||
path: 'ssh',
|
||||
key: 'update',
|
||||
payload: {
|
||||
exec: true,
|
||||
data: {
|
||||
remote: 'nisar',
|
||||
description: 'diana的项目ssl l关联',
|
||||
configs: [
|
||||
{
|
||||
localPort: 3000,
|
||||
remoteHost: 'localhost',
|
||||
remotePort: 3000,
|
||||
description: 'openweb ui',
|
||||
status: 'active',
|
||||
},
|
||||
{
|
||||
localPort: 5244,
|
||||
remoteHost: 'localhost',
|
||||
remotePort: 5244,
|
||||
description: 'alist',
|
||||
status: 'inactive',
|
||||
},
|
||||
{
|
||||
localPort: 3003,
|
||||
remoteHost: 'localhost',
|
||||
remotePort: 3003,
|
||||
description: 'onai api',
|
||||
status: 'inactive',
|
||||
},
|
||||
{
|
||||
localPort: 3002,
|
||||
remoteHost: 'localhost',
|
||||
remotePort: 3004,
|
||||
description: 'codeflow ui',
|
||||
status: 'inactive',
|
||||
},
|
||||
{
|
||||
localPort: 8000,
|
||||
remoteHost: 'localhost',
|
||||
remotePort: 8000,
|
||||
description: 'parsex python api',
|
||||
status: 'inactive',
|
||||
},
|
||||
{
|
||||
localPort: 9092,
|
||||
remoteHost: 'localhost',
|
||||
remotePort: 9092,
|
||||
description: 'kafka',
|
||||
status: 'inactive',
|
||||
},
|
||||
{
|
||||
localPort: 9200,
|
||||
remoteHost: 'localhost',
|
||||
remotePort: 9200,
|
||||
description: 'elasticsearch',
|
||||
status: 'inactive',
|
||||
},
|
||||
],
|
||||
},
|
||||
data: data,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
74
src/scripts/init-data.ts
Normal file
74
src/scripts/init-data.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { SSHModel } from '@/route/ssh/model.ts';
|
||||
export const data = {
|
||||
remote: 'nisar',
|
||||
description: 'diana的项目ssl -l关联',
|
||||
configs: [
|
||||
{
|
||||
localPort: 3000,
|
||||
remoteHost: 'localhost',
|
||||
remotePort: 3000,
|
||||
description: 'openweb ui',
|
||||
status: 'active',
|
||||
},
|
||||
{
|
||||
localPort: 5244,
|
||||
remoteHost: 'localhost',
|
||||
remotePort: 5244,
|
||||
description: 'alist',
|
||||
status: 'inactive',
|
||||
},
|
||||
{
|
||||
localPort: 3003,
|
||||
remoteHost: 'localhost',
|
||||
remotePort: 3003,
|
||||
description: 'onai py llm api',
|
||||
status: 'active',
|
||||
},
|
||||
{
|
||||
localPort: 3004,
|
||||
remoteHost: 'localhost',
|
||||
remotePort: 3004,
|
||||
description: 'onai ui',
|
||||
status: 'active',
|
||||
},
|
||||
{
|
||||
localPort: 4002,
|
||||
remoteHost: 'localhost',
|
||||
remotePort: 4002,
|
||||
description: 'codeflow api',
|
||||
status: 'active',
|
||||
},
|
||||
{
|
||||
localPort: 8000,
|
||||
remoteHost: 'localhost',
|
||||
remotePort: 8000,
|
||||
description: 'parsex python api',
|
||||
status: 'active',
|
||||
},
|
||||
{
|
||||
localPort: 9092,
|
||||
remoteHost: 'localhost',
|
||||
remotePort: 9092,
|
||||
description: 'kafka',
|
||||
status: 'inactive',
|
||||
},
|
||||
{
|
||||
localPort: 9200,
|
||||
remoteHost: 'localhost',
|
||||
remotePort: 9200,
|
||||
description: 'elasticsearch',
|
||||
status: 'active',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
const { remote, description, configs } = data;
|
||||
let ssh = await SSHModel.findOne({ where: { remote } });
|
||||
if (!ssh) {
|
||||
ssh = await SSHModel.create({ description, configs, remote });
|
||||
}
|
||||
await ssh.update({ description, configs }, { where: { remote } });
|
||||
};
|
||||
|
||||
main();
|
@ -10,6 +10,7 @@
|
||||
"baseUrl": "./",
|
||||
"typeRoots": [
|
||||
"node_modules/@types",
|
||||
"node_modules/@kevisual/types"
|
||||
],
|
||||
"declaration": true,
|
||||
"noEmit": false,
|
||||
|
Loading…
x
Reference in New Issue
Block a user