clear code
This commit is contained in:
parent
aadd8266b1
commit
43ce37b1ce
29
package.json
29
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kevisual/envision-cli",
|
||||
"version": "0.0.35",
|
||||
"version": "0.0.37",
|
||||
"description": "envision command tools",
|
||||
"main": "dist/app.mjs",
|
||||
"type": "module",
|
||||
@ -40,10 +40,9 @@
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-replace": "^6.0.2",
|
||||
"@rollup/plugin-typescript": "^12.1.2",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/jsonwebtoken": "^9.0.9",
|
||||
"@types/node": "^22.13.14",
|
||||
"@types/node": "^22.13.17",
|
||||
"chalk": "^5.4.1",
|
||||
"commander": "^13.1.0",
|
||||
"fast-glob": "^3.3.3",
|
||||
@ -51,17 +50,12 @@
|
||||
"form-data": "^4.0.2",
|
||||
"ignore": "^7.0.3",
|
||||
"inquirer": "^12.5.0",
|
||||
"rimraf": "^6.0.1",
|
||||
"rollup": "^4.38.0",
|
||||
"rollup-plugin-dts": "^6.2.1",
|
||||
"rollup": "^4.39.0",
|
||||
"rollup-plugin-esbuild": "^6.2.1",
|
||||
"tar": "^7.4.3",
|
||||
"tslib": "^2.8.1",
|
||||
"tsup": "^8.4.0",
|
||||
"typescript": "^5.8.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"picomatch": "^4"
|
||||
"@kevisual/load": "^0.0.6",
|
||||
"crypto-js": "^4.2.0",
|
||||
"jsonwebtoken": "^9.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.0.0"
|
||||
@ -69,14 +63,5 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kevisual/load": "^0.0.6",
|
||||
"@kevisual/router": "^0.0.9",
|
||||
"crypto-js": "^4.2.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"sequelize": "^6.37.7",
|
||||
"sqlite3": "^5.1.7",
|
||||
"vite": "^6.2.3"
|
||||
}
|
||||
"dependencies": {}
|
||||
}
|
1651
pnpm-lock.yaml
generated
1651
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,4 @@
|
||||
// rollup.config.js
|
||||
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import json from '@rollup/plugin-json';
|
||||
@ -11,7 +9,7 @@ import esbuild from 'rollup-plugin-esbuild';
|
||||
import alias from '@rollup/plugin-alias';
|
||||
import replace from '@rollup/plugin-replace';
|
||||
|
||||
const pkgs = JSON.parse(fs.readFileSync('./package.json', 'utf-8'));
|
||||
const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf-8'));
|
||||
/**
|
||||
* @type {import('rollup').RollupOptions}
|
||||
*/
|
||||
@ -26,7 +24,7 @@ const config = {
|
||||
plugins: [
|
||||
replace({
|
||||
preventAssignment: true, // 防止意外赋值
|
||||
VERSION: JSON.stringify(pkgs.version || '1.0.0'),
|
||||
VERSION: JSON.stringify(pkg.version || '1.0.0'),
|
||||
}),
|
||||
alias({
|
||||
// only esbuild needs to be configured
|
||||
@ -66,15 +64,7 @@ const config = {
|
||||
tsconfig: 'tsconfig.json',
|
||||
}),
|
||||
json(),
|
||||
// typescript({
|
||||
// allowImportingTsExtensions: true,
|
||||
// noEmit: true,
|
||||
// // 不生成声明文件
|
||||
// declaration: false,
|
||||
// }), // 使用 @rollup/plugin-typescript 处理 TypeScript 文件
|
||||
],
|
||||
// 将 sqlite3 作为外部依赖
|
||||
external: ['sqlite3', 'sequelize', 'vite', 'sequelize', '@kevisual/router', 'ioredis', 'socket.io', 'minio'],
|
||||
};
|
||||
|
||||
export default [config];
|
||||
|
@ -1,30 +0,0 @@
|
||||
import { App } from '@kevisual/router';
|
||||
import { app } from './app.ts';
|
||||
|
||||
type Message = {
|
||||
path: string;
|
||||
key?: string;
|
||||
payload?: any;
|
||||
};
|
||||
type Response = {
|
||||
code: number;
|
||||
data?: any;
|
||||
message?: string;
|
||||
};
|
||||
export const runApp = async (msg: Message): Promise<Response> => {
|
||||
try {
|
||||
const { code, body, message } = await app.call(msg);
|
||||
const res = { code, data: body };
|
||||
if (message) {
|
||||
res['message'] = message;
|
||||
}
|
||||
return res as { code: number; data: any; message?: string };
|
||||
} catch (error) {
|
||||
console.error('runApp error', error);
|
||||
return { code: 500, message: error.message };
|
||||
}
|
||||
};
|
||||
|
||||
export const loadApp = async (mainApp: App) => {
|
||||
mainApp.importApp(app);
|
||||
};
|
54
src/app.ts
54
src/app.ts
@ -1,54 +0,0 @@
|
||||
import { App } from '@kevisual/router';
|
||||
import fs from 'fs';
|
||||
import { getConfig, getPidList, pidFilePath, checkFileExists } from './module/get-config.ts';
|
||||
import { sequelize } from './module/sequelize.ts';
|
||||
export { sequelize };
|
||||
|
||||
export const app = new App({
|
||||
serverOptions: {
|
||||
cors: {
|
||||
origin: '*', // 允许所有来源
|
||||
},
|
||||
httpType: 'https',
|
||||
},
|
||||
});
|
||||
|
||||
// 处理进程退出或中断信号,确保删除 pid 文件
|
||||
const cleanUp = () => {
|
||||
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); // 退出进程
|
||||
};
|
||||
|
||||
// 当进程收到以下信号时,删除 pid 文件
|
||||
process.on('SIGINT', cleanUp); // 例如 Ctrl+C
|
||||
process.on('SIGTERM', cleanUp); // 终止信号
|
||||
process.on('exit', cleanUp); // 进程退出
|
||||
|
||||
export const createApp = async () => {
|
||||
if (checkFileExists(pidFilePath)) {
|
||||
const pid = fs.readFileSync(pidFilePath, 'utf-8');
|
||||
console.log('服务已经启动,请勿重复启动。', 'server id', pid);
|
||||
return;
|
||||
}
|
||||
fs.writeFileSync(pidFilePath, process.pid.toString());
|
||||
app.listen(21015, () => {
|
||||
console.log('Server is running on port 21015', 'https://localhost:21015/api/router', 'server id', process.pid);
|
||||
});
|
||||
// import('./route.ts');
|
||||
};
|
@ -119,3 +119,15 @@ const command = new Command('me')
|
||||
});
|
||||
|
||||
program.addCommand(command);
|
||||
|
||||
const logoutCommand = new Command('logout').description('退出登陆').action(async () => {
|
||||
try {
|
||||
await queryLogin.logout();
|
||||
storage.removeItem('token');
|
||||
console.log('退出成功');
|
||||
} catch (error) {
|
||||
console.log('退出失败', error);
|
||||
}
|
||||
});
|
||||
|
||||
program.addCommand(logoutCommand);
|
||||
|
@ -1,11 +0,0 @@
|
||||
import { program as app, Command } from '@/program.ts';
|
||||
import { getConfig, writeConfig } from '@/module/index.ts';
|
||||
|
||||
const command = new Command('logout')
|
||||
.description('')
|
||||
.action(async () => {
|
||||
const config = getConfig();
|
||||
writeConfig({ ...config, token: '' });
|
||||
});
|
||||
|
||||
app.addCommand(command);
|
@ -13,7 +13,7 @@ const tokenList = new Command('list')
|
||||
// .option('-r --remove <number>', 'remove token by number')
|
||||
.action(async (opts) => {
|
||||
console.log('show token list');
|
||||
queryLogin.cacheStore.init()
|
||||
queryLogin.cacheStore.init();
|
||||
// const res = await queryLogin.cacheStore.cache.get('token');
|
||||
console.log(queryLogin.cacheStore.cacheData);
|
||||
// console.log(util.inspect(res, { colors: true, depth: 4 }));
|
||||
@ -33,6 +33,11 @@ const baseURL = new Command('baseURL')
|
||||
.action(async (opts) => {
|
||||
let config = getConfig();
|
||||
let list = (config.baseURLList as Array<string>) || [];
|
||||
if (!config.baseURL) {
|
||||
list = ['https://kevisual.cn'];
|
||||
writeConfig({ ...config, baseURL: 'https://kevisual.cn', baseURLList: list });
|
||||
config = getConfig();
|
||||
}
|
||||
const quineList = (list: string[]) => {
|
||||
const newList = new Set(list);
|
||||
return Array.from(newList);
|
||||
|
@ -3,7 +3,6 @@ import inquirer from 'inquirer';
|
||||
import { query } from '../module/index.ts';
|
||||
import chalk from 'chalk';
|
||||
import util from 'util';
|
||||
import { runApp } from '@/app-run.ts';
|
||||
|
||||
const router = new Command('router').description('router get');
|
||||
program.addCommand(router);
|
||||
@ -58,45 +57,3 @@ const command = new Command('service')
|
||||
});
|
||||
|
||||
router.addCommand(command);
|
||||
|
||||
const localRouter = new Command('local')
|
||||
.description('router local get')
|
||||
.option('-p, --path <path>', '第一路径 path')
|
||||
.option('-k, --key <key>', '第二路径 key')
|
||||
.action(async (options) => {
|
||||
let { path, key } = options;
|
||||
// 如果没有传递参数,则通过交互式输入
|
||||
if (!path) {
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'path',
|
||||
required: true,
|
||||
message: 'Enter your path:',
|
||||
when: () => !path, // 当 username 为空时,提示用户输入
|
||||
},
|
||||
]);
|
||||
path = answers.path || path;
|
||||
}
|
||||
if (!key) {
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
required: false,
|
||||
name: 'key',
|
||||
message: 'Enter your key:',
|
||||
},
|
||||
]);
|
||||
key = answers.key || key;
|
||||
}
|
||||
const res = await runApp({ path, key });
|
||||
if (res?.code === 200) {
|
||||
console.log('query success');
|
||||
|
||||
console.log(chalk.green(util.inspect(res, { colors: true, depth: 4 })));
|
||||
} else {
|
||||
console.log('error query', res.code, res.message || '');
|
||||
}
|
||||
});
|
||||
|
||||
router.addCommand(localRouter);
|
||||
|
@ -1,40 +0,0 @@
|
||||
import { program as app, Command } from '@/program.ts';
|
||||
import { getConfig, writeConfig, pidFilePath, checkFileExists } from '@/module/index.ts';
|
||||
import { createApp } from '@/app.ts';
|
||||
import fs from 'fs';
|
||||
|
||||
const command = new Command('serve')
|
||||
.description('serve manager')
|
||||
.option('-e, --exit', '退出进程')
|
||||
.option('-s, --start', '启动进程')
|
||||
.action(async (options) => {
|
||||
console.log('options', options);
|
||||
if (options.exit) {
|
||||
if (checkFileExists(pidFilePath)) {
|
||||
console.log('服务已经启动, 准备退出');
|
||||
const pid = fs.readFileSync(pidFilePath, 'utf-8');
|
||||
// 通知进程退出
|
||||
process.kill(Number(pid), 'SIGTERM');
|
||||
setTimeout(() => {
|
||||
if (checkFileExists(pidFilePath)) {
|
||||
// 强制删除 pid 文件
|
||||
fs.unlinkSync(pidFilePath);
|
||||
}
|
||||
}, 2000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (options.start) {
|
||||
const config = getConfig();
|
||||
await createApp();
|
||||
}
|
||||
});
|
||||
|
||||
app.addCommand(command);
|
||||
|
||||
const start = new Command('start').description('start serve').action(async () => {
|
||||
const config = getConfig();
|
||||
await createApp();
|
||||
});
|
||||
|
||||
app.addCommand(start);
|
@ -1,71 +0,0 @@
|
||||
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);
|
@ -1,11 +1,8 @@
|
||||
import { program } from '@/program.ts';
|
||||
import './command/login.ts';
|
||||
import './command/logout.ts';
|
||||
import './command/ls-token.ts';
|
||||
import './command/deploy.ts';
|
||||
import './command/serve.ts';
|
||||
import './command/config.ts';
|
||||
import './command/web.ts';
|
||||
import './command/router.ts';
|
||||
import './command/npm.ts';
|
||||
import './command/publish.ts';
|
||||
|
@ -1,5 +1,6 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { storage } from '../query.ts';
|
||||
|
||||
type DownloadTask = {
|
||||
downloadPath: string;
|
||||
@ -62,8 +63,13 @@ export const installApp = async (app: Package, opts: InstallAppOpts = {}) => {
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
console.log('downloadUrl', downloadUrl);
|
||||
const res = await fetch(downloadUrl);
|
||||
console.log('downloadUrwl', downloadUrl);
|
||||
const token = process.env.KEVISUAL_TOKEN || storage.getItem('token');
|
||||
const fetchURL = new URL(downloadUrl);
|
||||
if (token) {
|
||||
fetchURL.searchParams.set('token', token);
|
||||
}
|
||||
const res = await fetch(fetchURL.toString());
|
||||
const blob = await res.blob();
|
||||
fs.writeFileSync(downloadPath, Buffer.from(await blob.arrayBuffer()));
|
||||
}
|
||||
|
@ -5,20 +5,6 @@ import fs from 'fs';
|
||||
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);
|
||||
@ -39,6 +25,9 @@ export const getConfig = () => {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
writeConfig({
|
||||
baseURL: 'https://kevisual.cn',
|
||||
});
|
||||
return {};
|
||||
};
|
||||
|
||||
|
@ -25,7 +25,11 @@ export const query = new Query({
|
||||
|
||||
query.beforeRequest = async (config) => {
|
||||
if (config.headers) {
|
||||
const token = await storage.getItem('token');
|
||||
let token = process.env.KEVISUAL_TOKEN;
|
||||
if (!token) {
|
||||
token = await storage.getItem('token');
|
||||
}
|
||||
|
||||
if (token) {
|
||||
config.headers['Authorization'] = 'Bearer ' + token;
|
||||
}
|
||||
|
@ -1,79 +0,0 @@
|
||||
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);
|
||||
};
|
@ -1,16 +0,0 @@
|
||||
import { Sequelize } from 'sequelize';
|
||||
import { dbPath } from './get-config.ts';
|
||||
// connect to db
|
||||
export const sequelize = new Sequelize({
|
||||
dialect: 'sqlite',
|
||||
// storage: 'db.sqlite',
|
||||
storage: dbPath,
|
||||
// logging: false,
|
||||
});
|
||||
|
||||
sequelize
|
||||
.authenticate({ logging: false })
|
||||
.then(() => {})
|
||||
.catch((err) => {
|
||||
console.error('Unable to connect to the database:', err);
|
||||
});
|
@ -1 +0,0 @@
|
||||
export * from './user/login.ts';
|
@ -1,40 +0,0 @@
|
||||
import { query } from '@/module/query.ts';
|
||||
|
||||
export const queryLogin = async (username: string, password: string) => {
|
||||
return await query.post({
|
||||
path: 'user',
|
||||
key: 'login',
|
||||
payload: {
|
||||
username,
|
||||
password,
|
||||
loginType: 'plugin',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const queryMe = async () => {
|
||||
return await query.post({
|
||||
path: 'user',
|
||||
key: 'me',
|
||||
});
|
||||
};
|
||||
|
||||
export const switchOrg = async (username) => {
|
||||
return await query.post({
|
||||
path: 'user',
|
||||
key: 'switchOrg',
|
||||
data: {
|
||||
loginType: 'plugin',
|
||||
username,
|
||||
},
|
||||
});
|
||||
};
|
||||
export const switchMe = async () => {
|
||||
return await query.post({
|
||||
path: 'user',
|
||||
key: 'switchOrg',
|
||||
data: {
|
||||
type: 'user',
|
||||
},
|
||||
});
|
||||
};
|
@ -1 +0,0 @@
|
||||
import './route/system-config/index.ts';
|
@ -1 +1 @@
|
||||
Subproject commit 98c8a2ad86331566058ca7cc12df5ee2b406fa2f
|
||||
Subproject commit f1024941eda4162b0ef5f9a4cd3b13c6645c7974
|
Loading…
x
Reference in New Issue
Block a user