Compare commits
11 Commits
c018ffd422
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 48f2695367 | |||
| 7d4bc37c09 | |||
| f3f1a1d058 | |||
| 4aeb3637bf | |||
| 5b83f7a6d1 | |||
| 9127df2600 | |||
| 8118daa4e2 | |||
| eca7b42377 | |||
| ee33208e6c | |||
| 94e331e376 | |||
| 77186a02a2 |
@@ -6,6 +6,7 @@ import fs from 'node:fs';
|
|||||||
// bun run src/index.ts --
|
// bun run src/index.ts --
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const external = ['pm2', '@kevisual/hot-api'];
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} p
|
* @param {string} p
|
||||||
@@ -20,11 +21,10 @@ await Bun.build({
|
|||||||
naming: {
|
naming: {
|
||||||
entry: 'assistant.js',
|
entry: 'assistant.js',
|
||||||
},
|
},
|
||||||
external: ['pm2'],
|
external,
|
||||||
define: {
|
define: {
|
||||||
ENVISION_VERSION: JSON.stringify(pkg.version),
|
ENVISION_VERSION: JSON.stringify(pkg.version),
|
||||||
},
|
},
|
||||||
env: 'ENVISION_*',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await Bun.build({
|
await Bun.build({
|
||||||
@@ -38,8 +38,7 @@ await Bun.build({
|
|||||||
define: {
|
define: {
|
||||||
ENVISION_VERSION: JSON.stringify(pkg.version),
|
ENVISION_VERSION: JSON.stringify(pkg.version),
|
||||||
},
|
},
|
||||||
external: ['pm2'],
|
external,
|
||||||
env: 'ENVISION_*',
|
|
||||||
});
|
});
|
||||||
// const copyDist = ['dist', 'bin'];
|
// const copyDist = ['dist', 'bin'];
|
||||||
const copyDist = ['dist'];
|
const copyDist = ['dist'];
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
],
|
],
|
||||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"packageManager": "pnpm@10.24.0",
|
"packageManager": "pnpm@10.25.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
@@ -20,8 +20,8 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun run src/run.ts ",
|
"dev": "bun run src/run.ts ",
|
||||||
"dev:server": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 bun --watch src/run-server.ts --home ",
|
"dev:server": "bun --watch src/run-server.ts ",
|
||||||
"dev:share": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 bun --watch src/test/remote-app.ts ",
|
"dev:share": "bun --watch src/test/remote-app.ts ",
|
||||||
"build:lib": "bun run bun-lib.config.mjs",
|
"build:lib": "bun run bun-lib.config.mjs",
|
||||||
"postbuild:lib": "dts -i src/lib.ts -o assistant-lib.d.ts -d libs -t",
|
"postbuild:lib": "dts -i src/lib.ts -o assistant-lib.d.ts -d libs -t",
|
||||||
"build": "rimraf dist && bun run bun.config.mjs",
|
"build": "rimraf dist && bun run bun.config.mjs",
|
||||||
@@ -41,18 +41,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@kevisual/ai": "^0.0.12",
|
"@kevisual/ai": "^0.0.19",
|
||||||
"@kevisual/load": "^0.0.6",
|
"@kevisual/load": "^0.0.6",
|
||||||
"@kevisual/local-app-manager": "^0.1.32",
|
"@kevisual/local-app-manager": "^0.1.32",
|
||||||
"@kevisual/logger": "^0.0.4",
|
"@kevisual/logger": "^0.0.4",
|
||||||
"@kevisual/query": "0.0.29",
|
"@kevisual/query": "0.0.32",
|
||||||
"@kevisual/query-login": "0.0.7",
|
"@kevisual/query-login": "0.0.7",
|
||||||
"@kevisual/router": "^0.0.33",
|
"@kevisual/router": "^0.0.37",
|
||||||
"@kevisual/types": "^0.0.10",
|
"@kevisual/types": "^0.0.10",
|
||||||
"@kevisual/use-config": "^1.0.21",
|
"@kevisual/use-config": "^1.0.21",
|
||||||
"@types/bun": "^1.3.3",
|
"@types/bun": "^1.3.4",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.2",
|
||||||
"@types/send": "^1.2.1",
|
"@types/send": "^1.2.1",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.19",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"get-port": "^7.1.0",
|
"get-port": "^7.1.0",
|
||||||
"inquirer": "^13.0.1",
|
"inquirer": "^13.0.2",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"nanoid": "^5.1.6",
|
"nanoid": "^5.1.6",
|
||||||
"send": "^1.2.0",
|
"send": "^1.2.0",
|
||||||
@@ -75,6 +75,8 @@
|
|||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@kevisual/hot-api": "^0.0.3",
|
||||||
|
"@nut-tree-fork/nut-js": "^4.2.6",
|
||||||
"eventemitter3": "^5.0.1",
|
"eventemitter3": "^5.0.1",
|
||||||
"lowdb": "^7.0.1",
|
"lowdb": "^7.0.1",
|
||||||
"lru-cache": "^11.2.4",
|
"lru-cache": "^11.2.4",
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import { spawnSync } from 'node:child_process';
|
|||||||
const command = new Command('server')
|
const command = new Command('server')
|
||||||
.description('启动服务')
|
.description('启动服务')
|
||||||
.option('-d, --daemon', '是否以守护进程方式运行')
|
.option('-d, --daemon', '是否以守护进程方式运行')
|
||||||
.option('-n, --name <name>', '服务名称')
|
.option('-n, --name <name>', '服务名称', 'assistant-server')
|
||||||
.option('-p, --port <port>', '服务端口')
|
.option('-p, --port <port>', '服务端口')
|
||||||
.option('-s, --start', '是否启动服务')
|
.option('-s, --start', '是否启动服务')
|
||||||
.option('-i, --home', '是否以home方式运行')
|
.option('-e, --interpreter <interpreter>', '指定使用的解释器', 'bun')
|
||||||
.action((options) => {
|
.action((options) => {
|
||||||
const { port } = options;
|
const { port } = options;
|
||||||
const [_interpreter, execPath] = process.argv;
|
const [_interpreter, execPath] = process.argv;
|
||||||
@@ -24,8 +24,8 @@ const command = new Command('server')
|
|||||||
if (port) {
|
if (port) {
|
||||||
shellCommands.push(`-p ${port}`);
|
shellCommands.push(`-p ${port}`);
|
||||||
}
|
}
|
||||||
if (options.home) {
|
if (options.interpreter) {
|
||||||
shellCommands.push('--home');
|
shellCommands.push(`-e ${options.interpreter}`);
|
||||||
}
|
}
|
||||||
const basename = _interpreter.split('/').pop();
|
const basename = _interpreter.split('/').pop();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export type ProxyInfo = {
|
export type ProxyInfo = {
|
||||||
/**
|
/**
|
||||||
* 代理路径, 比如/root/center, 匹配的路径
|
* 代理路径, 比如/root/home, 匹配的路径
|
||||||
*/
|
*/
|
||||||
path?: string;
|
path?: string;
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { LocalProxy, LocalProxyOpts } from './index.ts';
|
|||||||
import http from 'node:http';
|
import http from 'node:http';
|
||||||
import { fileProxy } from './proxy/file-proxy.ts';
|
import { fileProxy } from './proxy/file-proxy.ts';
|
||||||
const localProxy = new LocalProxy({});
|
const localProxy = new LocalProxy({});
|
||||||
let home = '/root/center';
|
let home = '/root/home';
|
||||||
export const initProxy = (data: LocalProxyOpts & { home?: string }) => {
|
export const initProxy = (data: LocalProxyOpts & { home?: string }) => {
|
||||||
localProxy.pagesDir = data.pagesDir || '';
|
localProxy.pagesDir = data.pagesDir || '';
|
||||||
localProxy.watch = data.watch ?? false;
|
localProxy.watch = data.watch ?? false;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export type ProxyInfo = {
|
export type ProxyInfo = {
|
||||||
/**
|
/**
|
||||||
* 代理路径, 比如/root/center, 匹配的路径
|
* 代理路径, 比如/root/home, 匹配的路径
|
||||||
*/
|
*/
|
||||||
path?: string;
|
path?: string;
|
||||||
/**
|
/**
|
||||||
|
|||||||
19
assistant/src/routes/hot-api/key-sender/index.ts
Normal file
19
assistant/src/routes/hot-api/key-sender/index.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { app } from '@/app.ts';
|
||||||
|
import { Hotkeys } from '@kevisual/hot-api';
|
||||||
|
import { useContextKey } from '@kevisual/context';
|
||||||
|
app.route({
|
||||||
|
path: 'key-sender',
|
||||||
|
// middleware: ['admin-auth']
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
let keys = ctx.query.keys;
|
||||||
|
if (keys.includes(' ')) {
|
||||||
|
keys = keys.replace(/\s+/g, '+');
|
||||||
|
}
|
||||||
|
const hotKeys: Hotkeys = useContextKey('hotkeys', () => new Hotkeys());
|
||||||
|
if (typeof keys === 'string') {
|
||||||
|
await hotKeys.pressHotkey({
|
||||||
|
hotkey: keys,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ctx.body = 'ok';
|
||||||
|
}).addTo(app);
|
||||||
@@ -2,8 +2,10 @@ import { app, assistantConfig } from '../app.ts';
|
|||||||
import './config/index.ts';
|
import './config/index.ts';
|
||||||
import './shop-install/index.ts';
|
import './shop-install/index.ts';
|
||||||
import './ai/index.ts';
|
import './ai/index.ts';
|
||||||
import './light-code/index.ts';
|
// TODO:
|
||||||
|
// import './light-code/index.ts';
|
||||||
import './user/index.ts';
|
import './user/index.ts';
|
||||||
|
import './hot-api/key-sender/index.ts';
|
||||||
|
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import { authCache } from '@/module/cache/auth.ts';
|
import { authCache } from '@/module/cache/auth.ts';
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { app, assistantConfig } from '@/app.ts';
|
import { app, assistantConfig } from '@/app.ts';
|
||||||
import { AppDownload } from '@/services/app/index.ts';
|
import { AppDownload } from '@/services/app/index.ts';
|
||||||
import { AssistantApp } from '@/module/assistant/index.ts';
|
import { AssistantApp } from '@/module/assistant/index.ts';
|
||||||
import { shopDefine } from './define.ts';
|
|
||||||
app
|
app
|
||||||
.route({
|
.route({
|
||||||
...shopDefine.get('getRegistry'),
|
path: 'shop',
|
||||||
|
key: 'get-registry',
|
||||||
|
description: '获取应用商店注册表信息',
|
||||||
middleware: ['admin-auth'],
|
middleware: ['admin-auth'],
|
||||||
metadata: {
|
metadata: {
|
||||||
admin: true,
|
admin: true,
|
||||||
@@ -19,7 +20,9 @@ app
|
|||||||
|
|
||||||
app
|
app
|
||||||
.route({
|
.route({
|
||||||
...shopDefine.get('listInstalled'),
|
path: 'shop',
|
||||||
|
key: 'list-installed',
|
||||||
|
description: '列出当前已安装的所有应用',
|
||||||
middleware: ['admin-auth'],
|
middleware: ['admin-auth'],
|
||||||
metadata: {
|
metadata: {
|
||||||
admin: true,
|
admin: true,
|
||||||
@@ -35,7 +38,9 @@ app
|
|||||||
|
|
||||||
app
|
app
|
||||||
.route({
|
.route({
|
||||||
...shopDefine.get('install'),
|
path: 'shop',
|
||||||
|
key: 'install',
|
||||||
|
description: '安装指定的应用,可以指定 id、type、force 和 yes 参数',
|
||||||
middleware: ['admin-auth'],
|
middleware: ['admin-auth'],
|
||||||
metadata: {
|
metadata: {
|
||||||
admin: true,
|
admin: true,
|
||||||
@@ -60,7 +65,9 @@ app
|
|||||||
|
|
||||||
app
|
app
|
||||||
.route({
|
.route({
|
||||||
...shopDefine.get('uninstall'),
|
path: 'shop',
|
||||||
|
key: 'uninstall',
|
||||||
|
description: '卸载指定的应用,可以指定 id 和 type 参数',
|
||||||
middleware: ['admin-auth'],
|
middleware: ['admin-auth'],
|
||||||
metadata: {
|
metadata: {
|
||||||
admin: true,
|
admin: true,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ app.route({
|
|||||||
}).define(async (ctx) => {
|
}).define(async (ctx) => {
|
||||||
const { username, password } = ctx.query;
|
const { username, password } = ctx.query;
|
||||||
const query = assistantConfig.query;
|
const query = assistantConfig.query;
|
||||||
const auth = assistantConfig.getConfig().auth;
|
const auth = assistantConfig.getConfig().auth || {};
|
||||||
const res = await query.post({
|
const res = await query.post({
|
||||||
path: 'user',
|
path: 'user',
|
||||||
key: 'login',
|
key: 'login',
|
||||||
@@ -25,7 +25,7 @@ app.route({
|
|||||||
}
|
}
|
||||||
if (!auth.username) {
|
if (!auth.username) {
|
||||||
// 初始管理员账号
|
// 初始管理员账号
|
||||||
auth.username = 'admin';
|
auth.username = loginUser;
|
||||||
assistantConfig.setConfig({ auth });
|
assistantConfig.setConfig({ auth });
|
||||||
}
|
}
|
||||||
// 保存配置
|
// 保存配置
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export type AssistantInitOptions = {
|
|||||||
path?: string;
|
path?: string;
|
||||||
init?: boolean;
|
init?: boolean;
|
||||||
};
|
};
|
||||||
|
const randomId = () => Math.random().toString(36).substring(2, 8);
|
||||||
/**
|
/**
|
||||||
* 助手初始化类
|
* 助手初始化类
|
||||||
* @class AssistantInit
|
* @class AssistantInit
|
||||||
@@ -41,6 +42,7 @@ export class AssistantInit extends AssistantConfig {
|
|||||||
this.createEnvConfig();
|
this.createEnvConfig();
|
||||||
this.createOtherConfig();
|
this.createOtherConfig();
|
||||||
this.initPnpm();
|
this.initPnpm();
|
||||||
|
this.initIgnore();
|
||||||
}
|
}
|
||||||
get query() {
|
get query() {
|
||||||
if (!this.#query) {
|
if (!this.#query) {
|
||||||
@@ -153,10 +155,46 @@ export class AssistantInit extends AssistantConfig {
|
|||||||
create,
|
create,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
initIgnore() {
|
||||||
|
const gitignorePath = path.join(this.configDir, '.gitignore');
|
||||||
|
let content = '';
|
||||||
|
if (checkFileExists(gitignorePath, true)) {
|
||||||
|
content = fs.readFileSync(gitignorePath, 'utf-8');
|
||||||
|
}
|
||||||
|
const ignoreLines = [
|
||||||
|
'node_modules',
|
||||||
|
'.DS_Store',
|
||||||
|
'dist',
|
||||||
|
'pack-dist',
|
||||||
|
'cache-file',
|
||||||
|
'build',
|
||||||
|
'apps/**/node_modules/',
|
||||||
|
'pages/**/node_modules/',
|
||||||
|
'.env',
|
||||||
|
'!.env*development',
|
||||||
|
'.pnpm-store',
|
||||||
|
'.vite',
|
||||||
|
'.astro'
|
||||||
|
];
|
||||||
|
let updated = false;
|
||||||
|
ignoreLines.forEach((line) => {
|
||||||
|
if (!content.includes(line)) {
|
||||||
|
content += `\n${line}`;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (updated) {
|
||||||
|
fs.writeFileSync(gitignorePath, content.trim() + '\n');
|
||||||
|
console.log(chalk.green('.gitignore 文件更新成功'));
|
||||||
|
}
|
||||||
|
}
|
||||||
protected getDefaultInitAssistantConfig() {
|
protected getDefaultInitAssistantConfig() {
|
||||||
|
const id = randomId();
|
||||||
return {
|
return {
|
||||||
|
id,
|
||||||
description: '助手配置文件',
|
description: '助手配置文件',
|
||||||
home: '/root/center',
|
docs: "https://kevisual.cn/root/cli-docs/",
|
||||||
|
home: '/root/home',
|
||||||
proxy: [],
|
proxy: [],
|
||||||
apiProxyList: [],
|
apiProxyList: [],
|
||||||
share: {
|
share: {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResp
|
|||||||
return res.end(`Not Found [${proxyApi.path}] rootPath`);
|
return res.end(`Not Found [${proxyApi.path}] rootPath`);
|
||||||
}
|
}
|
||||||
return fileProxy(req, res, {
|
return fileProxy(req, res, {
|
||||||
path: proxyApi.path, // 代理路径, 比如/root/center
|
path: proxyApi.path, // 代理路径, 比如/root/home
|
||||||
rootPath: proxyApi.rootPath,
|
rootPath: proxyApi.rootPath,
|
||||||
...proxyApi,
|
...proxyApi,
|
||||||
indexPath: _indexPath, // 首页路径
|
indexPath: _indexPath, // 首页路径
|
||||||
|
|||||||
20
package.json
20
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@kevisual/cli",
|
"name": "@kevisual/cli",
|
||||||
"version": "0.0.71",
|
"version": "0.0.74",
|
||||||
"description": "envision 命令行工具",
|
"description": "envision 命令行工具",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"basename": "/root/cli",
|
"basename": "/root/cli",
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun src/run.ts ",
|
"dev": "bun src/run.ts ",
|
||||||
"dev:tsx": "tsx src/run.ts ",
|
"dev:tsx": "tsx src/run.ts ",
|
||||||
|
"dev:server": "cd assistant && bun --watch src/run-server.ts ",
|
||||||
"build": "rimraf dist && bun run bun.config.mjs",
|
"build": "rimraf dist && bun run bun.config.mjs",
|
||||||
"deploy": "ev pack -u -p -m no",
|
"deploy": "ev pack -u -p -m no",
|
||||||
"pub:me": "npm publish --registry https://npm.xiongxiao.me --tag beta",
|
"pub:me": "npm publish --registry https://npm.xiongxiao.me --tag beta",
|
||||||
@@ -40,22 +41,29 @@
|
|||||||
],
|
],
|
||||||
"author": "abearxiong",
|
"author": "abearxiong",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@kevisual/app": "^0.0.1",
|
||||||
"@kevisual/context": "^0.0.4",
|
"@kevisual/context": "^0.0.4",
|
||||||
|
"@kevisual/hot-api": "^0.0.3",
|
||||||
|
"@nut-tree-fork/nut-js": "^4.2.6",
|
||||||
|
"eventemitter3": "^5.0.1",
|
||||||
|
"lowdb": "^7.0.1",
|
||||||
|
"lru-cache": "^11.2.4",
|
||||||
"micromatch": "^4.0.8",
|
"micromatch": "^4.0.8",
|
||||||
"pm2": "^6.0.14",
|
"pm2": "^6.0.14",
|
||||||
"semver": "^7.7.3"
|
"semver": "^7.7.3",
|
||||||
|
"unstorage": "^1.17.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@kevisual/dts": "^0.0.3",
|
"@kevisual/dts": "^0.0.3",
|
||||||
"@kevisual/load": "^0.0.6",
|
"@kevisual/load": "^0.0.6",
|
||||||
"@kevisual/logger": "^0.0.4",
|
"@kevisual/logger": "^0.0.4",
|
||||||
"@kevisual/query": "0.0.29",
|
"@kevisual/query": "0.0.32",
|
||||||
"@kevisual/query-login": "0.0.7",
|
"@kevisual/query-login": "0.0.7",
|
||||||
"@types/bun": "^1.3.3",
|
"@types/bun": "^1.3.4",
|
||||||
"@types/crypto-js": "^4.2.2",
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/jsonwebtoken": "^9.0.10",
|
"@types/jsonwebtoken": "^9.0.10",
|
||||||
"@types/micromatch": "^4.0.10",
|
"@types/micromatch": "^4.0.10",
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.2",
|
||||||
"@types/semver": "^7.7.1",
|
"@types/semver": "^7.7.1",
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
"commander": "^14.0.2",
|
"commander": "^14.0.2",
|
||||||
@@ -65,7 +73,7 @@
|
|||||||
"form-data": "^4.0.5",
|
"form-data": "^4.0.5",
|
||||||
"ignore": "^7.0.5",
|
"ignore": "^7.0.5",
|
||||||
"inquirer": "^13.0.2",
|
"inquirer": "^13.0.2",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.3",
|
||||||
"tar": "^7.5.2",
|
"tar": "^7.5.2",
|
||||||
"zustand": "^5.0.9"
|
"zustand": "^5.0.9"
|
||||||
},
|
},
|
||||||
|
|||||||
2380
pnpm-lock.yaml
generated
2380
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
4
src/ai/ai.ts
Normal file
4
src/ai/ai.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { App } from '@kevisual/app/src/app.ts';
|
||||||
|
import { storage } from '../module/query.ts';
|
||||||
|
|
||||||
|
export const app = new App({ token: storage.getItem('token') || '', storage });
|
||||||
6
src/ai/index.ts
Normal file
6
src/ai/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { app } from './ai.ts'
|
||||||
|
import './routes/cmd-run.ts'
|
||||||
|
|
||||||
|
export {
|
||||||
|
app
|
||||||
|
}
|
||||||
62
src/ai/routes/cmd-run.ts
Normal file
62
src/ai/routes/cmd-run.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { app } from '../ai.ts';
|
||||||
|
import { execSync } from 'node:child_process'
|
||||||
|
import { logger } from '@/module/logger.ts';
|
||||||
|
const promptTemplate = `# CMD 结果判断器
|
||||||
|
|
||||||
|
分析上一条 CMD 命令的执行结果,判断是否需要执行下一条命令。
|
||||||
|
|
||||||
|
- 若结果中隐含或明确指示需继续执行 → 返回:\`{"cmd": "推断出的下一条命令", "type": "cmd"}\`
|
||||||
|
- 若无后续操作,甚至上一次执行的返回为空或者成功 → 返回:\`{"type": "none"}\`
|
||||||
|
|
||||||
|
1. 仅输出合法 JSON,无任何额外文本。
|
||||||
|
2. \`cmd\` 必须从执行结果中合理推断得出,非预设或猜测。
|
||||||
|
3. 禁止解释、注释、换行或格式错误。`
|
||||||
|
|
||||||
|
app.router.route({
|
||||||
|
path: 'cmd-run',
|
||||||
|
description: '执行 CMD 命令并判断下一步操作, 参数是 cmd 字符串',
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
const cmd = ctx.query.cmd || '';
|
||||||
|
if (!cmd) {
|
||||||
|
ctx.throw(400, 'cmd is required');
|
||||||
|
}
|
||||||
|
let result = '';
|
||||||
|
ctx.state.steps = ctx.state?.steps || [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.info('执行命令:', cmd);
|
||||||
|
result = execSync(cmd, { encoding: 'utf-8' });
|
||||||
|
ctx.state.steps.push({ cmd, result });
|
||||||
|
logger.info(result);
|
||||||
|
} catch (error: any) {
|
||||||
|
result = error.message || '';
|
||||||
|
ctx.state.steps.push({ cmd, result, error: true });
|
||||||
|
ctx.body = {
|
||||||
|
steps: ctx.state.steps,
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await app.loadAI()
|
||||||
|
const prompt = `${promptTemplate}\n上一条命令:\n${cmd}\n执行结果:\n${result}\n`;
|
||||||
|
const response = await app.ai.question(prompt);
|
||||||
|
|
||||||
|
const msg = app.ai.utils.extractJsonFromMarkdown(app.ai.responseText);
|
||||||
|
try {
|
||||||
|
logger.debug('AI Prompt', prompt);
|
||||||
|
logger.debug('AI 分析结果:', msg);
|
||||||
|
const { cmd, type } = msg;
|
||||||
|
if (type === 'cmd' && cmd) {
|
||||||
|
await app.router.call({ path: 'cmd-run', payload: { cmd } }, { state: ctx.state });
|
||||||
|
} else {
|
||||||
|
logger.info('无后续命令,结束执行');
|
||||||
|
ctx.state.steps.push({ type: 'none' });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
result = '执行错误,无法解析返回结果为合法 JSON' + app.ai.responseText
|
||||||
|
logger.error(result);
|
||||||
|
ctx.state.steps.push({ cmd, result, parseError: true });
|
||||||
|
}
|
||||||
|
ctx.body = {
|
||||||
|
steps: ctx.state.steps,
|
||||||
|
}
|
||||||
|
}).addTo(app.router);
|
||||||
41
src/command/ai.ts
Normal file
41
src/command/ai.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { program, Command } from '@/program.ts';
|
||||||
|
import { app } from '../ai/index.ts';
|
||||||
|
import util from 'util';
|
||||||
|
import { chalk } from '@/module/chalk.ts';
|
||||||
|
import { logger } from '@/module/logger.ts';
|
||||||
|
const aiCmd = new Command('ai')
|
||||||
|
.description('AI 相关命令')
|
||||||
|
.action(async (opts) => {
|
||||||
|
});
|
||||||
|
|
||||||
|
const runCmd = async (cmd: string) => {
|
||||||
|
const res = await app.router.call({ path: 'cmd-run', payload: { cmd } });
|
||||||
|
const { body } = res;
|
||||||
|
const steps = body?.steps || [];
|
||||||
|
for (const step of steps) {
|
||||||
|
logger.debug(chalk.blue(`\n==== 步骤: ${step.cmd || '结束'} ====`));
|
||||||
|
logger.debug(step.result || 'No result');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const aiRun = new Command('run')
|
||||||
|
.description('执行 AI 命令')
|
||||||
|
.option('-c, --cmd <cmd>', '要执行的 CMD 命令')
|
||||||
|
.action(async (opts) => {
|
||||||
|
if (opts.cmd) {
|
||||||
|
await runCmd(opts.cmd);
|
||||||
|
} else {
|
||||||
|
console.log('请提供要执行的 CMD 命令');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const aiRunDeploy = new Command('deploy')
|
||||||
|
.description('部署 AI 后端应用')
|
||||||
|
.action(async (opts) => {
|
||||||
|
const cmd = 'ev pack -p -u';
|
||||||
|
const res = await runCmd(cmd);
|
||||||
|
});
|
||||||
|
|
||||||
|
aiCmd.addCommand(aiRun);
|
||||||
|
aiCmd.addCommand(aiRunDeploy);
|
||||||
|
|
||||||
|
program.addCommand(aiCmd);
|
||||||
78
src/command/config-secret-remote.ts
Normal file
78
src/command/config-secret-remote.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { program, Command } from '@/program.ts';
|
||||||
|
import { query } from '@/module/query.ts';
|
||||||
|
import { QueryConfig } from '@/query/query-secret/query-secret.ts';
|
||||||
|
import { showMore } from '@/uitls/show-more.ts';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
const queryConfig = new QueryConfig({ query: query as any });
|
||||||
|
const command = new Command('remote-secret')
|
||||||
|
.alias('rs').description('获取或设置远程配置');
|
||||||
|
|
||||||
|
|
||||||
|
const getCommand = new Command('get')
|
||||||
|
.option('-k, --key <key>', '配置键名')
|
||||||
|
.action(async (options) => {
|
||||||
|
const { key } = options || {};
|
||||||
|
if (!key) {
|
||||||
|
console.log('Please provide a key using -k or --key option.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const res = await queryConfig.getItem({ id: key });
|
||||||
|
console.log('res Config Result:', showMore(res.data));
|
||||||
|
})
|
||||||
|
|
||||||
|
const listCommand = new Command('list')
|
||||||
|
.description('列出所有配置')
|
||||||
|
.action(async () => {
|
||||||
|
const res = await queryConfig.listItems();
|
||||||
|
if (res.code === 200) {
|
||||||
|
const list = res.data?.list || [];
|
||||||
|
list.forEach(item => {
|
||||||
|
console.log(item.id, item.key, showMore(item));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('获取错误:', res.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
const updateCommand = new Command('update')
|
||||||
|
.description('更新远程配置')
|
||||||
|
.option('-i, --id <id>', '配置ID')
|
||||||
|
.option('-t, --title <title>', '配置值')
|
||||||
|
.option('-d, --description <description>', '配置数据,JSON格式')
|
||||||
|
.action(async (options) => {
|
||||||
|
const { id, title, description } = options || {};
|
||||||
|
let updateData: any = {};
|
||||||
|
if (title) {
|
||||||
|
updateData.title = title;
|
||||||
|
}
|
||||||
|
if (description) {
|
||||||
|
updateData.description = description;
|
||||||
|
}
|
||||||
|
if (id) {
|
||||||
|
updateData.id = id;
|
||||||
|
}
|
||||||
|
const res = await queryConfig.updateItem(updateData);
|
||||||
|
console.log('修改结果:', showMore(res));
|
||||||
|
});
|
||||||
|
const deleteCommand = new Command('delete')
|
||||||
|
.description('删除远程配置')
|
||||||
|
.option('-i, --id <id>', '配置ID')
|
||||||
|
.option('-k, --key <key>', '配置键名')
|
||||||
|
.action(async (options) => {
|
||||||
|
const { key, id } = options || {};
|
||||||
|
if (!key && !id) {
|
||||||
|
console.log('请提供配置键名或配置ID,使用 -k 或 --key 选项,或 -i 或 --id 选项。');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const res = await queryConfig.deleteItem({ key, id });
|
||||||
|
console.log('Delete Config Result:', showMore(res));
|
||||||
|
});
|
||||||
|
|
||||||
|
command.addCommand(listCommand);
|
||||||
|
command.addCommand(getCommand);
|
||||||
|
command.addCommand(updateCommand);
|
||||||
|
command.addCommand(deleteCommand);
|
||||||
|
|
||||||
|
program.addCommand(command);
|
||||||
@@ -30,7 +30,7 @@ export const getPackageJson = (opts?: { version?: string; appKey?: string }) =>
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const [user, appKey] = userAppArry;
|
const [user, appKey] = userAppArry;
|
||||||
return { basename, version, pkg: packageJson, user, appKey: appKey || opts?.appKey, app };
|
return { basename, version, pkg: packageJson, user, appKey: opts?.appKey || appKey, app };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,6 @@ const command = new Command('deploy')
|
|||||||
let { version, key, yes, update, org, showBackend } = options;
|
let { version, key, yes, update, org, showBackend } = options;
|
||||||
const noCheck = !options.noCheck;
|
const noCheck = !options.noCheck;
|
||||||
const dot = !!options.dot;
|
const dot = !!options.dot;
|
||||||
// 获取当前目录,是否存在package.json, 如果有,从package.json 获取 version 和basename
|
|
||||||
const pkgInfo = getPackageJson({ version, appKey: key });
|
const pkgInfo = getPackageJson({ version, appKey: key });
|
||||||
if (!version && pkgInfo?.version) {
|
if (!version && pkgInfo?.version) {
|
||||||
version = pkgInfo?.version || '';
|
version = pkgInfo?.version || '';
|
||||||
@@ -60,7 +59,7 @@ const command = new Command('deploy')
|
|||||||
if (!key && pkgInfo?.appKey) {
|
if (!key && pkgInfo?.appKey) {
|
||||||
key = pkgInfo?.appKey || '';
|
key = pkgInfo?.appKey || '';
|
||||||
}
|
}
|
||||||
console.log('start deploy');
|
logger.debug('start deploy');
|
||||||
if (!version || !key) {
|
if (!version || !key) {
|
||||||
const answers = await inquirer.prompt([
|
const answers = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
@@ -107,8 +106,8 @@ const command = new Command('deploy')
|
|||||||
const filename = path.basename(directory);
|
const filename = path.basename(directory);
|
||||||
_relativeFiles = [filename];
|
_relativeFiles = [filename];
|
||||||
}
|
}
|
||||||
console.log('upload Files', _relativeFiles);
|
logger.debug('upload Files', _relativeFiles);
|
||||||
console.log('upload Files Key', key, version);
|
logger.debug('upload Files Key', key, version);
|
||||||
if (!yes) {
|
if (!yes) {
|
||||||
// 确认是否上传
|
// 确认是否上传
|
||||||
const confirm = await inquirer.prompt([
|
const confirm = await inquirer.prompt([
|
||||||
@@ -125,7 +124,6 @@ const command = new Command('deploy')
|
|||||||
const uploadDirectory = isDirectory ? directory : path.dirname(directory);
|
const uploadDirectory = isDirectory ? directory : path.dirname(directory);
|
||||||
const res = await uploadFiles(_relativeFiles, uploadDirectory, { key, version, username: org, noCheckAppFiles: !noCheck, directory: options.directory });
|
const res = await uploadFiles(_relativeFiles, uploadDirectory, { key, version, username: org, noCheckAppFiles: !noCheck, directory: options.directory });
|
||||||
if (res?.code === 200) {
|
if (res?.code === 200) {
|
||||||
console.log('File uploaded successfully!');
|
|
||||||
res.data?.upload?.map?.((d) => {
|
res.data?.upload?.map?.((d) => {
|
||||||
console.log(chalk.green('uploaded file', d?.name, d?.path));
|
console.log(chalk.green('uploaded file', d?.name, d?.path));
|
||||||
});
|
});
|
||||||
@@ -140,7 +138,6 @@ const command = new Command('deploy')
|
|||||||
// const { id, data, ...rest } = res.data?.app || {};
|
// const { id, data, ...rest } = res.data?.app || {};
|
||||||
const { id, data, ...rest } = res2.data || {};
|
const { id, data, ...rest } = res2.data || {};
|
||||||
if (id && !update) {
|
if (id && !update) {
|
||||||
console.log(chalk.green('id: '), id);
|
|
||||||
if (!org) {
|
if (!org) {
|
||||||
console.log(chalk.green(`更新为最新版本: envision deploy-load ${id}`));
|
console.log(chalk.green(`更新为最新版本: envision deploy-load ${id}`));
|
||||||
} else {
|
} else {
|
||||||
@@ -153,9 +150,7 @@ const command = new Command('deploy')
|
|||||||
}
|
}
|
||||||
logger.debug('deploy success', res2.data);
|
logger.debug('deploy success', res2.data);
|
||||||
if (id && showBackend) {
|
if (id && showBackend) {
|
||||||
console.log('\n');
|
console.log(chalk.blue('下一个步骤服务端应用部署:\n'), 'envision pack-deploy', id);
|
||||||
console.log(chalk.blue('服务端应用部署: '), 'envision pack-deploy', id);
|
|
||||||
console.log('\n');
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('File upload failed', res?.message);
|
console.error('File upload failed', res?.message);
|
||||||
@@ -181,8 +176,8 @@ const uploadFiles = async (files: string[], directory: string, opts: UploadFileO
|
|||||||
const filePath = path.join(directory, file);
|
const filePath = path.join(directory, file);
|
||||||
const hash = getHash(filePath);
|
const hash = getHash(filePath);
|
||||||
if (!hash) {
|
if (!hash) {
|
||||||
console.error('文件', filePath, '不存在');
|
logger.error('文件', filePath, '不存在');
|
||||||
console.error('请检查文件是否存在');
|
logger.error('请检查文件是否存在');
|
||||||
}
|
}
|
||||||
data.files.push({ path: file, hash: hash });
|
data.files.push({ path: file, hash: hash });
|
||||||
}
|
}
|
||||||
@@ -221,11 +216,11 @@ const uploadFiles = async (files: string[], directory: string, opts: UploadFileO
|
|||||||
const filePath = path.join(directory, file);
|
const filePath = path.join(directory, file);
|
||||||
const check = checkData.find((d) => d.path === file);
|
const check = checkData.find((d) => d.path === file);
|
||||||
if (check?.isUpload) {
|
if (check?.isUpload) {
|
||||||
console.log('文件已经上传过了', file);
|
logger.debug('文件已经上传过了', file);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const filename = path.basename(filePath);
|
const filename = path.basename(filePath);
|
||||||
console.log('upload file', file, filename);
|
logger.debug('upload file', file, filename);
|
||||||
form.append('file', fs.createReadStream(filePath), {
|
form.append('file', fs.createReadStream(filePath), {
|
||||||
filename: filename,
|
filename: filename,
|
||||||
filepath: file,
|
filepath: file,
|
||||||
@@ -233,7 +228,7 @@ const uploadFiles = async (files: string[], directory: string, opts: UploadFileO
|
|||||||
needUpload = true;
|
needUpload = true;
|
||||||
}
|
}
|
||||||
if (!needUpload) {
|
if (!needUpload) {
|
||||||
console.log('所有文件都上传过了,不需要上传文件');
|
logger.debug('所有文件都上传过了,不需要上传文件');
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
};
|
};
|
||||||
@@ -261,16 +256,16 @@ const deployLoadFn = async (id: string, org?: string) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
console.log(chalk.green('deploy-load success. current version:', res.data?.version));
|
logger.info(chalk.green('deploy-load success. current version:', res.data?.version));
|
||||||
// /:username/:appName
|
// /:username/:appName
|
||||||
try {
|
try {
|
||||||
const { user, key } = res.data;
|
const { user, key } = res.data;
|
||||||
const baseURL = getBaseURL();
|
const baseURL = getBaseURL();
|
||||||
const deployURL = new URL(`/${user}/${key}/`, baseURL);
|
const deployURL = new URL(`/${user}/${key}/`, baseURL);
|
||||||
console.log(chalk.blue('deployURL', deployURL.href));
|
logger.info(chalk.blue('deployURL', deployURL.href));
|
||||||
} catch (error) { }
|
} catch (error) { }
|
||||||
} else {
|
} else {
|
||||||
console.error('deploy-load failed', res.message);
|
logger.error('deploy-load failed', res.message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -79,10 +79,10 @@ const showMe = async (show = true) => {
|
|||||||
const localToken = storage.getItem('token');
|
const localToken = storage.getItem('token');
|
||||||
if (!token && !localToken) {
|
if (!token && !localToken) {
|
||||||
console.log('请先登录');
|
console.log('请先登录');
|
||||||
return;
|
return { code: 40400, message: '请先登录' };
|
||||||
}
|
}
|
||||||
let me = await queryLogin.getMe(token);
|
let me = await queryLogin.getMe(token);
|
||||||
if (me.code === 401) {
|
if (me?.code === 401) {
|
||||||
me = await queryLogin.getMe();
|
me = await queryLogin.getMe();
|
||||||
}
|
}
|
||||||
if (show) {
|
if (show) {
|
||||||
@@ -113,6 +113,9 @@ const command = new Command('me')
|
|||||||
res = await showMe(false);
|
res = await showMe(false);
|
||||||
isRefresh = true;
|
isRefresh = true;
|
||||||
}
|
}
|
||||||
|
if (res.code === 40400) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
if (isRefresh) {
|
if (isRefresh) {
|
||||||
console.log(chalk.green('refresh token success'), '\n');
|
console.log(chalk.green('refresh token success'), '\n');
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { fileIsExist } from '@/uitls/file.ts';
|
|||||||
import { chalk } from '@/module/chalk.ts';
|
import { chalk } from '@/module/chalk.ts';
|
||||||
import * as backServices from '@/query/services/index.ts';
|
import * as backServices from '@/query/services/index.ts';
|
||||||
import inquirer from 'inquirer';
|
import inquirer from 'inquirer';
|
||||||
|
import { logger } from '@/module/logger.ts';
|
||||||
// 查找文件(忽略大小写)
|
// 查找文件(忽略大小写)
|
||||||
async function findFileInsensitive(targetFile: string): Promise<string | null> {
|
async function findFileInsensitive(targetFile: string): Promise<string | null> {
|
||||||
const files = fs.readdirSync('.');
|
const files = fs.readdirSync('.');
|
||||||
@@ -139,9 +140,9 @@ export const pack = async (opts: { packDist?: string, mergeDist?: boolean }) =>
|
|||||||
const allFiles = (await Promise.all(filesToInclude.map((file) => collectFileInfo(file)))).flat();
|
const allFiles = (await Promise.all(filesToInclude.map((file) => collectFileInfo(file)))).flat();
|
||||||
|
|
||||||
// 输出文件详细信息
|
// 输出文件详细信息
|
||||||
console.log('文件列表:');
|
logger.debug('文件列表:');
|
||||||
allFiles.forEach((file) => {
|
allFiles.forEach((file) => {
|
||||||
console.log(`${file.size}B ${file.path}`);
|
logger.debug(`${file.size}B ${file.path}`);
|
||||||
});
|
});
|
||||||
const totalSize = allFiles.reduce((sum, file) => sum + file.size, 0);
|
const totalSize = allFiles.reduce((sum, file) => sum + file.size, 0);
|
||||||
|
|
||||||
@@ -150,10 +151,10 @@ export const pack = async (opts: { packDist?: string, mergeDist?: boolean }) =>
|
|||||||
collection.totalSize = totalSize;
|
collection.totalSize = totalSize;
|
||||||
collection.tags = packageJson.app?.tags || packageJson.keywords || [];
|
collection.tags = packageJson.app?.tags || packageJson.keywords || [];
|
||||||
|
|
||||||
console.log('\n基本信息');
|
logger.debug('\n基本信息');
|
||||||
console.log(`name: ${packageJson.name}`);
|
logger.debug(`name: ${packageJson.name}`);
|
||||||
console.log(`version: ${packageJson.version}`);
|
logger.debug(`version: ${packageJson.version}`);
|
||||||
console.log(`total files: ${allFiles.length}`);
|
logger.debug(`total files: ${allFiles.length}`);
|
||||||
try {
|
try {
|
||||||
copyFilesToPackDist(filesToInclude, cwd, opts.packDist, mergeDist);
|
copyFilesToPackDist(filesToInclude, cwd, opts.packDist, mergeDist);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -188,7 +189,7 @@ const publishCommand = new Command('publish')
|
|||||||
console.log('发布逻辑实现', { key, version, config });
|
console.log('发布逻辑实现', { key, version, config });
|
||||||
});
|
});
|
||||||
|
|
||||||
const deployLoadFn = async (id: string, fileKey: string, force = false, install = false) => {
|
const deployLoadFn = async (id: string, fileKey: string, force = true, install = false) => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
console.error(chalk.red('id is required'));
|
console.error(chalk.red('id is required'));
|
||||||
return;
|
return;
|
||||||
@@ -222,7 +223,7 @@ const deployLoadFn = async (id: string, fileKey: string, force = false, install
|
|||||||
console.log('deploy-load success. current version:', res.data?.pkg?.version);
|
console.log('deploy-load success. current version:', res.data?.pkg?.version);
|
||||||
console.log('run: ', 'envision services -s', res.data?.showAppInfo?.key);
|
console.log('run: ', 'envision services -s', res.data?.showAppInfo?.key);
|
||||||
} else {
|
} else {
|
||||||
console.error('deploy-load failed', res.message);
|
console.error('deploy-load 失败', res.message);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
@@ -303,7 +304,7 @@ const packCommand = new Command('pack')
|
|||||||
if (yes) {
|
if (yes) {
|
||||||
deployCommand.push('-y', 'yes');
|
deployCommand.push('-y', 'yes');
|
||||||
}
|
}
|
||||||
console.log(chalk.blue('deploy doing: '), deployCommand.slice(2).join(' '), '\n');
|
logger.debug(chalk.blue('deploy doing: '), deployCommand.slice(2).join(' '), '\n');
|
||||||
// console.log('pack deploy services', chalk.blue('example: '), runDeployCommand);
|
// console.log('pack deploy services', chalk.blue('example: '), runDeployCommand);
|
||||||
|
|
||||||
program.parse(deployCommand);
|
program.parse(deployCommand);
|
||||||
@@ -312,11 +313,10 @@ const packCommand = new Command('pack')
|
|||||||
const packDeployCommand = new Command('pack-deploy')
|
const packDeployCommand = new Command('pack-deploy')
|
||||||
.argument('<id>', 'id')
|
.argument('<id>', 'id')
|
||||||
.option('-k, --key <key>', 'fileKey, 服务器的部署文件夹的列表')
|
.option('-k, --key <key>', 'fileKey, 服务器的部署文件夹的列表')
|
||||||
.option('-f --force', 'force')
|
|
||||||
.option('-i, --install ', 'install dependencies')
|
.option('-i, --install ', 'install dependencies')
|
||||||
.action(async (id, opts) => {
|
.action(async (id, opts) => {
|
||||||
let { force, key, install } = opts || {};
|
let { key, install } = opts || {};
|
||||||
const res = await deployLoadFn(id, key, force, install);
|
const res = await deployLoadFn(id, key, true, install);
|
||||||
});
|
});
|
||||||
|
|
||||||
program.addCommand(packDeployCommand);
|
program.addCommand(packDeployCommand);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import { Config, SyncList, SyncConfigType } from './type.ts';
|
import { Config, SyncList, SyncConfigType, SyncConfig } from './type.ts';
|
||||||
import { fileIsExist } from '@/uitls/file.ts';
|
import { fileIsExist } from '@/uitls/file.ts';
|
||||||
import { getHash } from '@/uitls/hash.ts';
|
import { getHash } from '@/uitls/hash.ts';
|
||||||
import glob from 'fast-glob';
|
import glob from 'fast-glob';
|
||||||
@@ -32,6 +32,15 @@ export class SyncBase {
|
|||||||
this.baseURL = opts?.baseURL ?? '';
|
this.baseURL = opts?.baseURL ?? '';
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
get dir() {
|
||||||
|
return this.#dir;
|
||||||
|
}
|
||||||
|
get configFilename() {
|
||||||
|
return this.#filename;
|
||||||
|
}
|
||||||
|
get configPath() {
|
||||||
|
return path.join(this.#dir, this.#filename);
|
||||||
|
}
|
||||||
async init() {
|
async init() {
|
||||||
try {
|
try {
|
||||||
const dir = this.#dir;
|
const dir = this.#dir;
|
||||||
@@ -59,7 +68,8 @@ export class SyncBase {
|
|||||||
if (!filename) return false;
|
if (!filename) return false;
|
||||||
const dir = this.#dir;
|
const dir = this.#dir;
|
||||||
const file = path.join(dir, filename);
|
const file = path.join(dir, filename);
|
||||||
return { relative: path.relative(dir, file), absolute: file };
|
const realFilename = path.basename(filename);
|
||||||
|
return { relative: path.relative(dir, file), absolute: file, filename: realFilename };
|
||||||
}
|
}
|
||||||
async canDone(syncType: SyncConfigType, type?: SyncConfigType) {
|
async canDone(syncType: SyncConfigType, type?: SyncConfigType) {
|
||||||
if (syncType === 'sync') return true;
|
if (syncType === 'sync') return true;
|
||||||
@@ -120,38 +130,67 @@ export class SyncBase {
|
|||||||
return syncList;
|
return syncList;
|
||||||
}
|
}
|
||||||
async getCheckList() {
|
async getCheckList() {
|
||||||
const checkDir = this.config?.checkDir || {};
|
const checkDir = this.config?.clone || {};
|
||||||
const dirKeys = Object.keys(checkDir);
|
const dirKeys = Object.keys(checkDir);
|
||||||
|
const registry = this.config?.registry || '';
|
||||||
const files = dirKeys.map((key) => {
|
const files = dirKeys.map((key) => {
|
||||||
return { key, ...this.getRelativePath(key) };
|
return { key, ...this.getRelativePath(key) };
|
||||||
});
|
});
|
||||||
return files
|
return files
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
let auth = checkAuth(checkDir[item.key]?.url, this.baseURL);
|
let url = checkDir[item.key]?.url || registry;
|
||||||
|
let auth = checkAuth(url, this.baseURL);
|
||||||
return {
|
return {
|
||||||
key: item.key,
|
key: item.key,
|
||||||
...checkDir[item.key],
|
...checkDir[item.key],
|
||||||
|
url: url,
|
||||||
filepath: item?.absolute,
|
filepath: item?.absolute,
|
||||||
auth,
|
auth,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((item) => item);
|
.filter((item) => item);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* sync 是已有的,优先级高于 fileSync
|
||||||
|
*
|
||||||
|
* @param sync
|
||||||
|
* @param fileSync
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
getMergeSync(sync: Config['sync'] = {}, fileSync: Config['sync'] = {}) {
|
getMergeSync(sync: Config['sync'] = {}, fileSync: Config['sync'] = {}) {
|
||||||
const syncFileSyncKeys = Object.keys(fileSync);
|
const syncFileSyncKeys = Object.keys(fileSync);
|
||||||
const syncKeys = Object.keys(sync);
|
const syncKeys = Object.keys(sync);
|
||||||
|
const config = this.config!;
|
||||||
|
const registry = config?.registry;
|
||||||
const keys = [...syncKeys, ...syncFileSyncKeys];
|
const keys = [...syncKeys, ...syncFileSyncKeys];
|
||||||
const obj: Config['sync'] = {};
|
const obj: Config['sync'] = {};
|
||||||
|
const wrapperRegistry = (value: SyncConfig | string) => {
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
const url = value.url;
|
||||||
|
if (registry && !url.startsWith('http')) {
|
||||||
|
return {
|
||||||
|
...value,
|
||||||
|
url: registry.replace(/\/+$/g, '') + '/' + url.replace(/^\/+/g, ''),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
const url = value;
|
||||||
|
if (registry && !url.startsWith('http')) {
|
||||||
|
return registry.replace(/\/+$/g, '') + '/' + url.replace(/^\/+/g, '');
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
for (let key of keys) {
|
for (let key of keys) {
|
||||||
const value = sync[key] ?? fileSync[key];
|
const value = sync[key] ?? fileSync[key];
|
||||||
obj[key] = value;
|
obj[key] = wrapperRegistry(value);
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
async getSyncDirectoryList() {
|
async getSyncDirectoryList() {
|
||||||
const config = this.config;
|
const config = this.config;
|
||||||
const syncDirectory = config?.syncDirectory || [];
|
const syncDirectory = config?.syncd || [];
|
||||||
let obj: Record<string, any> = {};
|
let obj: Record<string, any> = {};
|
||||||
const keys: string[] = [];
|
const keys: string[] = [];
|
||||||
for (let item of syncDirectory) {
|
for (let item of syncDirectory) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export type SyncDirectory = {
|
|||||||
**/
|
**/
|
||||||
ignore?: string[];
|
ignore?: string[];
|
||||||
/**
|
/**
|
||||||
* 合并路径的源地址,https://kevisual.xiongxiao.me/root/ai/kevisual
|
* 合并路径的源地址,https://kevisual.cn/root/ai/kevisual
|
||||||
*/
|
*/
|
||||||
registry?: string;
|
registry?: string;
|
||||||
files?: string[];
|
files?: string[];
|
||||||
@@ -21,13 +21,13 @@ export type SyncDirectory = {
|
|||||||
export interface Config {
|
export interface Config {
|
||||||
name?: string; // 项目名称
|
name?: string; // 项目名称
|
||||||
version?: string; // 项目版本号
|
version?: string; // 项目版本号
|
||||||
registry?: string; // 项目仓库地址
|
registry?: string; // 当前模块的root
|
||||||
metadata?: Record<string, any>; // 元数据, 统一的配置
|
metadata?: Record<string, any>; // 元数据, 统一的配置
|
||||||
syncDirectory?: SyncDirectory[];
|
syncd?: SyncDirectory[];
|
||||||
sync?: {
|
sync?: {
|
||||||
[key: string]: SyncConfig | string;
|
[key: string]: SyncConfig | string;
|
||||||
};
|
};
|
||||||
checkDir?: {
|
clone?: {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
url: string; // 需要检查的 url
|
url: string; // 需要检查的 url
|
||||||
replace?: Record<string, string>; // 替换的路径
|
replace?: Record<string, string>; // 替换的路径
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { program as app, Command } from '@/program.ts';
|
import { program as app, Command } from '@/program.ts';
|
||||||
import { SyncBase } from './modules/base.ts';
|
import { SyncBase } from './modules/base.ts';
|
||||||
import { baseURL, storage } from '@/module/query.ts';
|
import { baseURL, query, storage } from '@/module/query.ts';
|
||||||
import { fetchLink, fetchAiList } from '@/module/download/install.ts';
|
import { fetchLink, fetchAiList } from '@/module/download/install.ts';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import { upload } from '@/module/download/upload.ts';
|
import { upload } from '@/module/download/upload.ts';
|
||||||
import { logger } from '@/module/logger.ts';
|
import { logger, printClickableLink } from '@/module/logger.ts';
|
||||||
import { chalk } from '@/module/chalk.ts';
|
import { chalk } from '@/module/chalk.ts';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { fileIsExist } from '@/uitls/file.ts';
|
import { fileIsExist } from '@/uitls/file.ts';
|
||||||
@@ -124,7 +124,9 @@ const syncList = new Command('list')
|
|||||||
syncList.forEach((item) => {
|
syncList.forEach((item) => {
|
||||||
if (opts.all) {
|
if (opts.all) {
|
||||||
logger.info(item);
|
logger.info(item);
|
||||||
} else logger.info(chalk.blue(item.key), chalk.gray(item.type), chalk.green(item.url));
|
} else {
|
||||||
|
logger.info(chalk.green(printClickableLink({ url: item.url, text: item.key, print: false })), chalk.gray(item.type));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const syncCreateList = new Command('create')
|
const syncCreateList = new Command('create')
|
||||||
@@ -154,16 +156,26 @@ const syncCreateList = new Command('create')
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const checkDir = new Command('check')
|
const clone = new Command('clone')
|
||||||
.option('-d --dir <dir>', '配置目录')
|
.option('-d --dir <dir>', '配置目录')
|
||||||
.option('-c --config <config>', '配置文件的名字', 'kevisual.json')
|
.option('-c --config <config>', '配置文件的名字', 'kevisual.json')
|
||||||
|
.option('-i --link <link>', '克隆链接, 比 kevisual.json 优先级更高')
|
||||||
.description('检查目录')
|
.description('检查目录')
|
||||||
.action(async (opts) => {
|
.action(async (opts) => {
|
||||||
|
const link = opts.link || '';
|
||||||
const sync = new SyncBase({ dir: opts.dir, baseURL: baseURL, configFilename: opts.config });
|
const sync = new SyncBase({ dir: opts.dir, baseURL: baseURL, configFilename: opts.config });
|
||||||
|
if (link) {
|
||||||
|
const res = await query.fetchText(link);
|
||||||
|
if (res.code === 200) {
|
||||||
|
fs.writeFileSync(sync.configPath, JSON.stringify(res.data, null, 2));
|
||||||
|
}
|
||||||
|
sync.init()
|
||||||
|
}
|
||||||
const syncList = await sync.getSyncList();
|
const syncList = await sync.getSyncList();
|
||||||
logger.debug(syncList);
|
logger.debug(syncList);
|
||||||
logger.info('检查目录\n');
|
logger.info('检查目录\n');
|
||||||
const checkList = await sync.getCheckList();
|
const checkList = await sync.getCheckList();
|
||||||
|
logger.info('检查列表', checkList);
|
||||||
for (const item of checkList) {
|
for (const item of checkList) {
|
||||||
if (!item.auth) {
|
if (!item.auth) {
|
||||||
continue;
|
continue;
|
||||||
@@ -184,7 +196,19 @@ const checkDir = new Command('check')
|
|||||||
const matchList = matchObjectList
|
const matchList = matchObjectList
|
||||||
.map((item2) => {
|
.map((item2) => {
|
||||||
const rp = sync.getRelativePath(item2.pathname);
|
const rp = sync.getRelativePath(item2.pathname);
|
||||||
|
|
||||||
if (!rp) return false;
|
if (!rp) return false;
|
||||||
|
if (rp.absolute.endsWith('gitignore.txt')) {
|
||||||
|
// 修改为 .gitignore
|
||||||
|
const newPath = rp.absolute.replace('gitignore.txt', '.gitignore');
|
||||||
|
rp.absolute = newPath;
|
||||||
|
rp.relative = path.relative(sync.dir, newPath);
|
||||||
|
} else if (rp.absolute.endsWith('.dot')) {
|
||||||
|
const filename = path.basename(rp.absolute, '.dot');
|
||||||
|
const newPath = path.join(path.dirname(rp.absolute), `.${filename}`);
|
||||||
|
rp.absolute = newPath;
|
||||||
|
rp.relative = path.relative(sync.dir, newPath);
|
||||||
|
}
|
||||||
return { ...item2, relative: rp.relative, absolute: rp.absolute };
|
return { ...item2, relative: rp.relative, absolute: rp.absolute };
|
||||||
})
|
})
|
||||||
.filter((i) => i);
|
.filter((i) => i);
|
||||||
@@ -226,6 +250,6 @@ command.addCommand(syncUpload);
|
|||||||
command.addCommand(syncDownload);
|
command.addCommand(syncDownload);
|
||||||
command.addCommand(syncList);
|
command.addCommand(syncList);
|
||||||
command.addCommand(syncCreateList);
|
command.addCommand(syncCreateList);
|
||||||
command.addCommand(checkDir);
|
command.addCommand(clone);
|
||||||
|
|
||||||
app.addCommand(command);
|
app.addCommand(command);
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ import './command/app/index.ts';
|
|||||||
|
|
||||||
import './command/gist/index.ts';
|
import './command/gist/index.ts';
|
||||||
import './command/config-remote.ts';
|
import './command/config-remote.ts';
|
||||||
|
import './command/config-secret-remote.ts';
|
||||||
|
import './command/ai.ts';
|
||||||
// program.parse(process.argv);
|
// program.parse(process.argv);
|
||||||
|
|
||||||
export const runParser = async (argv: string[]) => {
|
export const runParser = async (argv: string[]) => {
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
import { Logger } from '@kevisual/logger/node';
|
import { Logger } from '@kevisual/logger/node';
|
||||||
|
|
||||||
const level = process.env.LOG_LEVEL || 'info';
|
const level = process.env.LOG_LEVEL || 'info';
|
||||||
export const logger = new Logger({
|
export const logger = new Logger({
|
||||||
level: level as any,
|
level: level as any,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export function printClickableLink({ url, text, print = true }: { url: string; text: string, print?: boolean }) {
|
||||||
|
const escape = '\x1B'; // ESC 字符
|
||||||
|
const linkStart = `${escape}]8;;${url}${escape}\\`;
|
||||||
|
const linkEnd = `${escape}]8;;${escape}\\`;
|
||||||
|
if (print) {
|
||||||
|
console.log(`${linkStart}${text}${linkEnd}`);
|
||||||
|
}
|
||||||
|
return `${linkStart}${text}${linkEnd}`;
|
||||||
|
}
|
||||||
|
|||||||
65
src/query/query-secret/query-secret.ts
Normal file
65
src/query/query-secret/query-secret.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* 配置查询
|
||||||
|
* @updatedAt 2025-12-03 10:33:00
|
||||||
|
*/
|
||||||
|
import { Query } from '@kevisual/query';
|
||||||
|
import type { Result } from '@kevisual/query/query';
|
||||||
|
type QueryConfigOpts = {
|
||||||
|
query?: Query;
|
||||||
|
};
|
||||||
|
export type Config<T = any> = {
|
||||||
|
id?: string;
|
||||||
|
title?: string;
|
||||||
|
key?: string;
|
||||||
|
description?: string;
|
||||||
|
data?: T;
|
||||||
|
createdAt?: string;
|
||||||
|
updatedAt?: string;
|
||||||
|
};
|
||||||
|
export type UploadConfig = {
|
||||||
|
key?: string;
|
||||||
|
version?: string;
|
||||||
|
};
|
||||||
|
type PostOpts = {
|
||||||
|
token?: string;
|
||||||
|
payload?: Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class QueryConfig {
|
||||||
|
query: Query;
|
||||||
|
constructor(opts?: QueryConfigOpts) {
|
||||||
|
this.query = opts?.query || new Query();
|
||||||
|
}
|
||||||
|
async post<T = Config>(data: any) {
|
||||||
|
return this.query.post<T>({ path: 'secret', ...data });
|
||||||
|
}
|
||||||
|
async getItem({ id, key }: { id?: string; key?: string }, opts?: PostOpts) {
|
||||||
|
return this.post({
|
||||||
|
key: 'get',
|
||||||
|
data: {
|
||||||
|
id,
|
||||||
|
key,
|
||||||
|
},
|
||||||
|
...opts,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async updateItem(data: Config, opts?: PostOpts) {
|
||||||
|
return this.post({
|
||||||
|
key: 'update',
|
||||||
|
data,
|
||||||
|
...opts,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async deleteItem(data: { id?: string, key?: string }, opts?: PostOpts) {
|
||||||
|
return this.post({
|
||||||
|
key: 'delete',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async listItems(opts?: PostOpts) {
|
||||||
|
return this.post<{ list: Config[] }>({
|
||||||
|
key: 'list',
|
||||||
|
...opts,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user