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
|
.env
|
||||||
/logs
|
/logs
|
||||||
|
|
||||||
|
cache
|
58
package.json
58
package.json
@ -1,30 +1,62 @@
|
|||||||
{
|
{
|
||||||
"name": "dev-app",
|
"name": "dev-app",
|
||||||
"version": "0.0.1",
|
"version": "0.0.2",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"app": {
|
"app": {
|
||||||
"type": "micro-app"
|
"type": "system-app",
|
||||||
|
"entry": "dist/app.mjs",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"scripts": {
|
"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": [],
|
"keywords": [],
|
||||||
"author": "abearxiong <xiongxiao@xiongxiao.me>",
|
"author": "abearxiong <xiongxiao@xiongxiao.me>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"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": {
|
"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",
|
"esbuild": "^0.24.0",
|
||||||
"nanoid": "^5.0.9",
|
"nanoid": "^5.0.9",
|
||||||
"sequelize": "^6.37.5",
|
"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 { App } from '@kevisual/router';
|
||||||
|
import { DevManater } from './modules/manager.ts';
|
||||||
export const app = new App();
|
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 { app } from './app.ts';
|
||||||
import './route/ssh/list.ts';
|
import './route/ssh/list.ts';
|
||||||
|
import './route/dev/index.ts';
|
||||||
|
|
||||||
app
|
if (DEV_SERVER) {
|
||||||
.call({
|
// app
|
||||||
path: 'ssh',
|
// .call({
|
||||||
key: 'list',
|
// path: 'ssh',
|
||||||
})
|
// key: 'list',
|
||||||
.then((res) => {
|
// })
|
||||||
console.log(res);
|
// .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 { DataTypes, Model, Op } from 'sequelize';
|
||||||
import { sequelize } from '@/modules/sequelize.ts';
|
import { sequelize } from '@/modules/sequelize.ts';
|
||||||
|
|
||||||
type DevData = {
|
export type DevData = {
|
||||||
cwd?: string; // 当前工作目录
|
cwd?: string; // 当前工作目录
|
||||||
command: string; // 启动命令
|
command: string; // 启动命令
|
||||||
type: 'node' | 'shell' | 'script'; // 启动类型
|
type: 'shell' | 'script'; // 启动类型
|
||||||
env?: Record<string, string>; // 环境变量
|
env?: Record<string, string>; // 环境变量
|
||||||
keepalive?: boolean; // 是否保持运行, 让用户知道是不是保持运行的
|
keepalive?: boolean; // 是否保持运行, 让用户知道是不是保持运行的
|
||||||
code?: string; // 代码
|
code?: string; // 代码
|
||||||
status?: 'active' | 'inactive'; // 状态
|
codeHash?: string; // 代码hash
|
||||||
path?: string; // esbuild 打包路径,用于 node 启动
|
codePath?: string; // 代码路径, 转成 code 的路径
|
||||||
|
codeStatus?: 'success' | 'error'; // 转换状态
|
||||||
|
status: 'active' | 'inactive'; // 状态, 是否生成codePath
|
||||||
|
path?: string; // esbuild 打包路径,用于 node 启动, vite 配置 esbuild 配置,rollup配置路径等
|
||||||
};
|
};
|
||||||
export type Dev = Partial<InstanceType<typeof DevModel>>;
|
export type Dev = Partial<InstanceType<typeof DevModel>>;
|
||||||
|
|
||||||
@ -19,6 +22,7 @@ export type Dev = Partial<InstanceType<typeof DevModel>>;
|
|||||||
export class DevModel extends Model {
|
export class DevModel extends Model {
|
||||||
declare id: string;
|
declare id: string;
|
||||||
declare title: string;
|
declare title: string;
|
||||||
|
declare key: string;
|
||||||
declare description: string;
|
declare description: string;
|
||||||
declare data: DevData;
|
declare data: DevData;
|
||||||
}
|
}
|
||||||
@ -34,6 +38,11 @@ DevModel.init(
|
|||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
},
|
},
|
||||||
|
key: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: '',
|
||||||
|
},
|
||||||
description: {
|
description: {
|
||||||
type: DataTypes.TEXT,
|
type: DataTypes.TEXT,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
@ -48,13 +57,12 @@ DevModel.init(
|
|||||||
sequelize,
|
sequelize,
|
||||||
paranoid: true,
|
paranoid: true,
|
||||||
modelName: 'local_dev',
|
modelName: 'local_dev',
|
||||||
freezeTableName: true, // 禁用表名复数化
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
DevModel.sync({
|
await DevModel.sync({
|
||||||
alter: true,
|
alter: true,
|
||||||
logging: false,
|
logging: false,
|
||||||
}).catch((e) => {
|
}).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);
|
sshManager.createServer(ssh.remote, ssh.configs);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
try {
|
||||||
|
if (!DEV_SERVER) {
|
||||||
setTimeout(init, 1000);
|
setTimeout(init, 1000);
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
app
|
app
|
||||||
.route({
|
.route({
|
||||||
path: 'ssh',
|
path: 'ssh',
|
||||||
|
@ -11,7 +11,6 @@ export type SSH = Partial<InstanceType<typeof SSHModel>>;
|
|||||||
|
|
||||||
export class SSHModel extends Model {
|
export class SSHModel extends Model {
|
||||||
declare id: string;
|
declare id: string;
|
||||||
// declare title: string;
|
|
||||||
declare description: string;
|
declare description: string;
|
||||||
declare configs: SSHModelData[];
|
declare configs: SSHModelData[];
|
||||||
declare remote: string;
|
declare remote: string;
|
||||||
@ -23,11 +22,6 @@ SSHModel.init(
|
|||||||
primaryKey: true,
|
primaryKey: true,
|
||||||
defaultValue: DataTypes.UUIDV4,
|
defaultValue: DataTypes.UUIDV4,
|
||||||
},
|
},
|
||||||
// title: {
|
|
||||||
// type: DataTypes.TEXT,
|
|
||||||
// allowNull: false,
|
|
||||||
// defaultValue: '',
|
|
||||||
// },
|
|
||||||
description: {
|
description: {
|
||||||
type: DataTypes.TEXT,
|
type: DataTypes.TEXT,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
@ -47,11 +41,10 @@ SSHModel.init(
|
|||||||
sequelize,
|
sequelize,
|
||||||
paranoid: true,
|
paranoid: true,
|
||||||
modelName: 'local_ssh',
|
modelName: 'local_ssh',
|
||||||
freezeTableName: true, // 禁用表名复数化
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
SSHModel.sync({
|
await SSHModel.sync({
|
||||||
alter: true,
|
alter: true,
|
||||||
logging: false,
|
logging: false,
|
||||||
}).catch((e) => {
|
}).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 { app } from '../app.ts';
|
||||||
import '../index.ts';
|
import '../index.ts';
|
||||||
const sleep = (time: number) => new Promise((resolve) => setTimeout(resolve, time));
|
const sleep = (time: number) => new Promise((resolve) => setTimeout(resolve, time));
|
||||||
|
import { data } from './init-data.ts';
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
await sleep(3000);
|
await sleep(3000);
|
||||||
// console.log('add nisar')
|
// console.log('add nisar')
|
||||||
|
|
||||||
app.call({
|
app.call({
|
||||||
path: 'ssh',
|
path: 'ssh',
|
||||||
key: 'update',
|
key: 'update',
|
||||||
payload: {
|
payload: {
|
||||||
exec: true,
|
exec: true,
|
||||||
data: {
|
data: 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',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
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": "./",
|
"baseUrl": "./",
|
||||||
"typeRoots": [
|
"typeRoots": [
|
||||||
"node_modules/@types",
|
"node_modules/@types",
|
||||||
|
"node_modules/@kevisual/types"
|
||||||
],
|
],
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"noEmit": false,
|
"noEmit": false,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user