feat: add dev module
This commit is contained in:
		
							
								
								
									
										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,
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user