feat: add vite dev containers

This commit is contained in:
2024-10-21 01:36:34 +08:00
parent 644539b624
commit c832c0274a
12 changed files with 590 additions and 14 deletions

View File

@@ -1,6 +1,6 @@
import { App } from '@kevisual/router';
import fs from 'fs';
import { getConfig, pidFilePath, checkFileExists } from './module/get-config.ts';
import { getConfig, getPidList, pidFilePath, checkFileExists } from './module/get-config.ts';
import { sequelize } from './module/sequelize.ts';
export { sequelize };
@@ -28,11 +28,21 @@ app
// 处理进程退出或中断信号,确保删除 pid 文件
const cleanUp = () => {
if (checkFileExists(pidFilePath)) {
const pid = fs.readFileSync(pidFilePath, 'utf-8');
if (Number(pid) === process.pid) {
fs.unlinkSync(pidFilePath);
console.log('server id', process.pid, 'is exit');
const pidList = getPidList();
const findPid = pidList.find((item) => Number(item.pid) === process.pid);
// if (checkFileExists(pidFilePath)) {
// const pid = fs.readFileSync(pidFilePath, 'utf-8');
// if (Number(pid) === process.pid) {
// fs.unlinkSync(pidFilePath);
// console.log('server id', process.pid, 'is exit');
// }
// }
if (findPid) {
console.log('server id', process.pid, 'is exit');
try {
fs.unlinkSync(findPid.file);
} catch (e) {
console.log('unlinkSync error', findPid);
}
}
process.exit(0); // 退出进程

View File

@@ -1,24 +1,51 @@
import { program as app, Command } from '@/program.ts';
import { getConfig, writeConfig } from '@/module/index.ts';
import { checkFileExists, getConfig, writeConfig } from '@/module/index.ts';
import path from 'path';
import fs from 'fs';
const command = new Command('config')
.description('')
.option('-d, --dev <dev>', 'Specify dev')
.option('-l --list', 'list config')
.option('-w --workdir <path>', 'web config')
.action(async (options) => {
const { dev, list } = options || {};
const { dev, list, workdir } = options || {};
let config = getConfig();
let flag = false;
if (dev === 'true' || dev === 'false') {
flag = true;
const config = getConfig();
if (dev === 'true') {
writeConfig({ ...config, dev: true });
config.dev = true;
} else {
writeConfig({ ...config, dev: false });
config.dev = false;
}
}
if (options.workdir) {
let finalPath: string;
flag = true;
const workdir = options.workdir;
if (workdir.startsWith('/')) {
// 如果以 / 开头,处理为绝对路径
finalPath = workdir;
} else {
// 否则,处理为相对路径
finalPath = path.resolve(workdir);
}
if (!checkFileExists(finalPath)) {
console.log('路径不存在');
fs.mkdirSync(finalPath, { recursive: true });
}
config.workdir = finalPath;
}
if (list) {
const config = getConfig();
console.log('config', config);
}
if (flag) {
writeConfig(config);
}
});
app.addCommand(command);

View File

@@ -53,7 +53,7 @@ const loginCommand = new Command('login')
} else {
console.log('登录失败', res.message || '');
}
console.log('u', username, password);
console.log('welcome', username);
});
app.addCommand(loginCommand);

52
src/command/router.ts Normal file
View File

@@ -0,0 +1,52 @@
import { program as app, Command } from '@/program.ts';
import { getConfig, writeConfig, checkFileExists } from '@/module/index.ts';
import { createApp } from '@/app.ts';
import fs from 'fs';
import inquirer from 'inquirer';
import { query } from '../module/index.ts';
import chalk from 'chalk';
import util from 'util';
// web 开发模块
const command = new Command('router')
.description('router get')
.option('-p, --path <path>', '退出进程')
.option('-k, --key <key>', '启动进程')
.action(async (options) => {
let { path, key } = options;
// 如果没有传递参数,则通过交互式输入
if (!path || !key) {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'path',
message: 'Enter your path:',
when: () => !path, // 当 username 为空时,提示用户输入
},
{
type: 'input',
name: 'key',
message: 'Enter your key:',
when: () => !key, // 当 password 为空时,提示用户输入
},
]);
path = answers.path || path;
key = answers.key || key;
}
const res = await query.post({ path, key });
if (res?.code === 200) {
const data = res.data.map((item: any) => {
// return `id: ${item.id}, title: ${item.title}`;
return {
id: item.id,
title: item.title,
};
});
console.log(chalk.green(util.inspect(data, { colors: true, depth: 4 })));
} else {
console.log('error', res.message || '');
}
});
app.addCommand(command);

71
src/command/web.ts Normal file
View File

@@ -0,0 +1,71 @@
import { program as app, Command } from '@/program.ts';
import { getConfig, writeConfig, checkFileExists, query } from '@/module/index.ts';
import fs from 'fs';
import path from 'path';
import { startContainerServer } from '@/module/run-vite.ts';
// web 开发模块
const command = new Command('web')
.description('web dev manager')
.option('-e, --exit', '退出进程')
.option('-s, --start', '启动进程')
.action(async (options) => {
const { exit, start } = options || {};
});
app.addCommand(command);
// container 开发模块
const container = new Command('container')
.description('container manager')
.option('-d, --container <id>', '下载镜像')
.option('-f, --force', '下载镜像')
.option('-u, --upload <id>', '上传镜像')
.action(async (options) => {
const { container, upload, force } = options || {};
const config = getConfig();
const workdir = config.workdir;
if (!workdir) {
console.log('请先配置工作目录');
return;
}
if (!config.token) {
console.log('请先登录');
return;
}
// 32210aa6-3d5a-4687-b769-ae4e8137ec1e
if (container) {
console.log('下载镜像', container);
const res = await query.post({ path: 'container', key: 'get', id: container });
if (res.code !== 200) {
console.log('error', res.message || '');
return;
}
await startContainerServer(res.data, force);
}
if (upload) {
console.log('上传镜像', upload);
const directory = path.join(workdir, 'container', upload);
if (!checkFileExists(directory)) {
console.log('文件夹不存在');
return;
}
const code = fs.readFileSync(path.join(directory, 'index.js'), 'utf-8');
if (!code) {
console.log('文件不能为空');
return;
}
const res = await query.post({
path: 'container', //
key: 'update',
data: { id: upload, code },
});
if (res.code !== 200) {
console.log('error', res.message || '');
console.log(res);
return;
}
console.log('上传成功');
}
});
app.addCommand(container);

View File

@@ -6,5 +6,7 @@ import './command/me.ts';
import './command/deploy.ts';
import './command/serve.ts';
import './command/config.ts';
import './command/web.ts';
import './command/router.ts';
program.parse(process.argv);

View File

@@ -2,12 +2,23 @@ import os from 'os';
import path from 'path';
import fs from 'fs';
const envisionPath = path.join(os.homedir(), '.config', 'envision');
export const envisionPath = path.join(os.homedir(), '.config', 'envision');
const configPath = path.join(os.homedir(), '.config', 'envision', 'config.json');
export const pidFilePath = path.join(envisionPath, 'app.pid');
export const dbPath = path.join(envisionPath, 'db.sqlite');
const envisionPidDir = path.join(envisionPath);
export const getPidList = () => {
const files = fs.readdirSync(envisionPidDir);
const pidFiles = files.filter((file) => file.endsWith('.pid'));
return pidFiles.map((file) => {
const pid = fs.readFileSync(path.join(envisionPidDir, file), 'utf-8');
return { pid, file: path.join(envisionPidDir, file) };
});
};
export const writeVitePid = (pid: number) => {
fs.writeFileSync(path.join(envisionPath, `vite-${pid}.pid`), pid.toString());
};
export const checkFileExists = (filePath: string) => {
try {
fs.accessSync(filePath, fs.constants.F_OK);

79
src/module/run-vite.ts Normal file
View File

@@ -0,0 +1,79 @@
import fs from 'fs';
import path from 'path';
import { createServer } from 'vite';
import { checkFileExists, getConfig, writeVitePid } from './index.ts';
export const runVite = async (entry: string) => {
const entryDir = path.dirname(entry);
const server = await createServer({
// Vite 配置选项
root: entryDir,
server: {
port: 7101, // 可以根据需要设置端口
host: '0.0.0.0',
},
});
await server.listen();
console.log('Vite server is running at:', server.config.server.port);
};
const template = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="https://envision.xiongxiao.me/resources/root/avatar.png"/>
<title>Container Develop</title>
<style>
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
body {
font-size: 16px;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="module">
import { ContainerOne } from 'https://kevisual.xiongxiao.me/system/lib/container.js'
import { render, unmount } from './index.js'
const container = new ContainerOne({
root: '#root',
});
container.renderOne({
code: {render, unmount}
});
</script>
</body>
</html>`;
export const startContainerServer = async (container: any, force: boolean) => {
const { id, code } = container;
const config = getConfig();
const workdir = config.workdir;
if (!workdir) {
console.log('请先配置工作目录');
return;
}
if (!config.token) {
console.log('请先登录');
return;
}
const directory = path.join(workdir, 'container', id);
if (!checkFileExists(directory) || force) {
fs.mkdirSync(directory, { recursive: true });
fs.writeFileSync(path.join(directory, 'index.js'), code);
fs.writeFileSync(path.join(directory, 'index.html'), template);
} else {
console.log('文件夹已存在');
}
await runVite(path.join(directory, 'index.html'));
console.log('container server is running at:', 'http://localhost:7101');
console.log('pid:', process.pid);
writeVitePid(process.pid);
};