Compare commits
12 Commits
31a7a99dbd
...
94e4db8d44
| Author | SHA1 | Date | |
|---|---|---|---|
| 94e4db8d44 | |||
| cb8e709c5d | |||
| 8867951893 | |||
| 2d4666ce90 | |||
| ad494248cc | |||
| 5594123cc7 | |||
| 3b383639d6 | |||
| c2c6d4a7d3 | |||
| 57bf884360 | |||
| 73d0fb9730 | |||
| acd88fb815 | |||
| e5e04e0cc9 |
@@ -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.30.2",
|
"packageManager": "pnpm@10.31.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
@@ -43,18 +43,18 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@inquirer/prompts": "^8.3.0",
|
"@inquirer/prompts": "^8.3.0",
|
||||||
"@kevisual/ai": "^0.0.24",
|
"@kevisual/ai": "^0.0.26",
|
||||||
"@kevisual/api": "^0.0.60",
|
"@kevisual/api": "^0.0.62",
|
||||||
"@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.52",
|
"@kevisual/query": "0.0.53",
|
||||||
"@kevisual/router": "^0.0.84",
|
"@kevisual/router": "^0.0.88",
|
||||||
"@kevisual/types": "^0.0.12",
|
"@kevisual/types": "^0.0.12",
|
||||||
"@kevisual/use-config": "^1.0.30",
|
"@kevisual/use-config": "^1.0.30",
|
||||||
"@opencode-ai/plugin": "^1.2.14",
|
"@opencode-ai/plugin": "^1.2.21",
|
||||||
"@types/bun": "^1.3.9",
|
"@types/bun": "^1.3.10",
|
||||||
"@types/node": "^25.3.0",
|
"@types/node": "^25.3.5",
|
||||||
"@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",
|
||||||
@@ -76,12 +76,12 @@
|
|||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.997.0",
|
"@aws-sdk/client-s3": "^3.1004.0",
|
||||||
"@kevisual/js-filter": "^0.0.5",
|
"@kevisual/js-filter": "^0.0.5",
|
||||||
"@kevisual/oss": "^0.0.19",
|
"@kevisual/oss": "^0.0.20",
|
||||||
"@kevisual/video-tools": "^0.0.13",
|
"@kevisual/video-tools": "^0.0.13",
|
||||||
"@opencode-ai/sdk": "^1.2.14",
|
"@opencode-ai/sdk": "^1.2.21",
|
||||||
"es-toolkit": "^1.44.0",
|
"es-toolkit": "^1.45.1",
|
||||||
"eventemitter3": "^5.0.4",
|
"eventemitter3": "^5.0.4",
|
||||||
"lowdb": "^7.0.1",
|
"lowdb": "^7.0.1",
|
||||||
"lru-cache": "^11.2.6",
|
"lru-cache": "^11.2.6",
|
||||||
|
|||||||
@@ -381,13 +381,22 @@ export class AssistantConfig {
|
|||||||
protected getDefaultInitAssistantConfig() {
|
protected getDefaultInitAssistantConfig() {
|
||||||
const id = randomId();
|
const id = randomId();
|
||||||
const isCNB = !!useKey('CNB');
|
const isCNB = !!useKey('CNB');
|
||||||
|
let kevisualUrl = 'https://kevisual.cn';
|
||||||
|
if (isCNB) {
|
||||||
|
const uri = getCNBUrl();
|
||||||
|
if (uri) {
|
||||||
|
kevisualUrl = uri;
|
||||||
|
} else {
|
||||||
|
kevisualUrl = 'http://kevisual.cn';
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
app: {
|
app: {
|
||||||
url: 'https://kevisual.cn',
|
url: kevisualUrl,
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
description: '助手配置文件',
|
description: '助手配置文件',
|
||||||
docs: "https://kevisual.cn/root/cli/docs/",
|
docs: `${kevisualUrl}/root/cli/docs/`,
|
||||||
home: isCNB ? '/root/cli-center' : '/root/home',
|
home: isCNB ? '/root/cli-center' : '/root/home',
|
||||||
proxy: [],
|
proxy: [],
|
||||||
share: {
|
share: {
|
||||||
@@ -414,3 +423,13 @@ export const parseIfJson = (content: string) => {
|
|||||||
export * from './args.ts';
|
export * from './args.ts';
|
||||||
|
|
||||||
const randomId = () => Math.random().toString(36).substring(2, 8);
|
const randomId = () => Math.random().toString(36).substring(2, 8);
|
||||||
|
|
||||||
|
export const getCNBUrl = () => {
|
||||||
|
const isCNB = !!useKey('CNB');
|
||||||
|
const uri = useKey('CNB_VSCODE_PROXY_URI') as string;
|
||||||
|
if (!isCNB) return null
|
||||||
|
if (uri) {
|
||||||
|
return uri.replace('{{port}}', '51515');
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
@@ -130,10 +130,11 @@ export class AssistantApp extends Manager {
|
|||||||
}
|
}
|
||||||
const url = new URL(shareUrl);
|
const url = new URL(shareUrl);
|
||||||
const id = config?.app?.id;
|
const id = config?.app?.id;
|
||||||
if (token && url && id) {
|
if (url && id) {
|
||||||
const remoteApp = new RemoteApp({
|
const remoteApp = new RemoteApp({
|
||||||
url: url.toString(),
|
url: url.toString(),
|
||||||
token,
|
token,
|
||||||
|
username: config?.auth?.username || '',
|
||||||
id,
|
id,
|
||||||
app: this.mainApp,
|
app: this.mainApp,
|
||||||
// 使用 RemoteApp 内置的自动重连机制
|
// 使用 RemoteApp 内置的自动重连机制
|
||||||
|
|||||||
@@ -162,8 +162,8 @@ export const initLightCode = async (opts: Opts) => {
|
|||||||
metadata.source = 'light-code';
|
metadata.source = 'light-code';
|
||||||
metadata['light-code'] = {
|
metadata['light-code'] = {
|
||||||
id: file.id
|
id: file.id
|
||||||
}
|
};
|
||||||
app.route({
|
(app as App).route({
|
||||||
id: routerItem.id,
|
id: routerItem.id,
|
||||||
path: `${routerItem.id}__${routerItem.path}`,
|
path: `${routerItem.id}__${routerItem.path}`,
|
||||||
key: routerItem.key,
|
key: routerItem.key,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ type RemoteAppOptions = {
|
|||||||
app?: App;
|
app?: App;
|
||||||
url?: string;
|
url?: string;
|
||||||
token?: string;
|
token?: string;
|
||||||
|
username?: string;
|
||||||
emitter?: EventEmitter;
|
emitter?: EventEmitter;
|
||||||
id?: string;
|
id?: string;
|
||||||
/** 是否启用自动重连,默认 true */
|
/** 是否启用自动重连,默认 true */
|
||||||
@@ -24,8 +25,10 @@ export class RemoteApp {
|
|||||||
mainApp: App;
|
mainApp: App;
|
||||||
url: string;
|
url: string;
|
||||||
id: string;
|
id: string;
|
||||||
|
username: string;
|
||||||
emitter: EventEmitter;
|
emitter: EventEmitter;
|
||||||
isConnected: boolean;
|
isConnected: boolean;
|
||||||
|
isVerified: boolean;
|
||||||
ws: WebSocket;
|
ws: WebSocket;
|
||||||
remoteIsConnected: boolean;
|
remoteIsConnected: boolean;
|
||||||
isError: boolean = false;
|
isError: boolean = false;
|
||||||
@@ -43,12 +46,17 @@ export class RemoteApp {
|
|||||||
const token = opts.token;
|
const token = opts.token;
|
||||||
const url = opts.url;
|
const url = opts.url;
|
||||||
const id = opts.id;
|
const id = opts.id;
|
||||||
|
const username = opts.username;
|
||||||
|
this.username = username;
|
||||||
this.emitter = opts?.emitter || new EventEmitter();
|
this.emitter = opts?.emitter || new EventEmitter();
|
||||||
const _url = new URL(url);
|
const _url = new URL(url);
|
||||||
if (token) {
|
if (token) {
|
||||||
_url.searchParams.set('token', token);
|
_url.searchParams.set('token', token);
|
||||||
}
|
}
|
||||||
_url.searchParams.set('id', id);
|
_url.searchParams.set('id', id);
|
||||||
|
if (!token && !username) {
|
||||||
|
console.error(`[remote-app] 不存在用户名和token ${id}. 权限认证会失败。`);
|
||||||
|
}
|
||||||
this.url = _url.toString();
|
this.url = _url.toString();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
// 初始化重连相关配置
|
// 初始化重连相关配置
|
||||||
@@ -78,11 +86,25 @@ export class RemoteApp {
|
|||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
that.isConnected = true;
|
that.isConnected = true;
|
||||||
that.remoteIsConnected = true;
|
that.remoteIsConnected = true;
|
||||||
|
|
||||||
resolve(true);
|
resolve(true);
|
||||||
};
|
};
|
||||||
that.emitter.once('open', listenOnce);
|
that.emitter.once('open', listenOnce);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
async waitVerify(): Promise<boolean> {
|
||||||
|
if (this.isVerified) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 等待验证成功
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const listenOnce = () => {
|
||||||
|
this.isVerified = true;
|
||||||
|
resolve(true);
|
||||||
|
};
|
||||||
|
this.emitter.once('verified', listenOnce);
|
||||||
|
});
|
||||||
|
}
|
||||||
getWsURL(url: string) {
|
getWsURL(url: string) {
|
||||||
const { protocol } = new URL(url);
|
const { protocol } = new URL(url);
|
||||||
if (protocol.startsWith('ws')) {
|
if (protocol.startsWith('ws')) {
|
||||||
@@ -223,6 +245,7 @@ export class RemoteApp {
|
|||||||
listenProxy() {
|
listenProxy() {
|
||||||
const remoteApp = this;
|
const remoteApp = this;
|
||||||
const app = this.mainApp;
|
const app = this.mainApp;
|
||||||
|
const username = this.username;
|
||||||
const listenFn = async (event: any) => {
|
const listenFn = async (event: any) => {
|
||||||
try {
|
try {
|
||||||
const data = event.toString();
|
const data = event.toString();
|
||||||
@@ -230,12 +253,18 @@ export class RemoteApp {
|
|||||||
const bodyData = body?.data as ListenProcessParams;
|
const bodyData = body?.data as ListenProcessParams;
|
||||||
const message = bodyData?.message || {};
|
const message = bodyData?.message || {};
|
||||||
const context = bodyData?.context || {};
|
const context = bodyData?.context || {};
|
||||||
|
console.log('[remote-app] 远程应用收到消息:', body);
|
||||||
if (body?.code === 401) {
|
if (body?.code === 401) {
|
||||||
console.error('远程应用认证失败,请检查 token 是否正确');
|
console.error('[remote-app] 远程应用认证失败,请检查 token 是否正确');
|
||||||
this.isError = true;
|
this.isError = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (body?.type === 'verified') {
|
||||||
|
remoteApp.emitter.emit('verified');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (body?.type !== 'proxy') return;
|
if (body?.type !== 'proxy') return;
|
||||||
|
|
||||||
if (!body.id) {
|
if (!body.id) {
|
||||||
remoteApp.json({
|
remoteApp.json({
|
||||||
id: body.id,
|
id: body.id,
|
||||||
@@ -257,13 +286,15 @@ export class RemoteApp {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('处理远程代理请求出错:', error);
|
console.error('[remote-app] 处理远程代理请求出错:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
remoteApp.json({
|
remoteApp.json({
|
||||||
id: this.id,
|
id: this.id,
|
||||||
type: 'registryClient'
|
type: 'registryClient',
|
||||||
|
username: username,
|
||||||
});
|
});
|
||||||
|
console.log(`[remote-app] 远程应用 ${this.id} (${username}) 已注册到主应用,等待消息...`);
|
||||||
remoteApp.emitter.on('message', listenFn);
|
remoteApp.emitter.on('message', listenFn);
|
||||||
const closeMessage = () => {
|
const closeMessage = () => {
|
||||||
remoteApp.emitter.off('message', listenFn);
|
remoteApp.emitter.off('message', listenFn);
|
||||||
|
|||||||
@@ -46,9 +46,9 @@ export const getLiveMdContent = (opts?: { more?: boolean }) => {
|
|||||||
|
|
||||||
### 其他说明
|
### 其他说明
|
||||||
|
|
||||||
|
1. 保活说明
|
||||||
使用插件访问vscode web获取wss进行保活,避免长时间不操作导致的自动断开连接。
|
使用插件访问vscode web获取wss进行保活,避免长时间不操作导致的自动断开连接。
|
||||||
|
|
||||||
保活说明
|
|
||||||
方法1: 使用插件访问vscode web获取wss进行保活,避免长时间不操作导致的自动断开连接。
|
方法1: 使用插件访问vscode web获取wss进行保活,避免长时间不操作导致的自动断开连接。
|
||||||
|
|
||||||
1. 安装插件[CNB LIVE](https://chromewebstore.google.com/detail/cnb-live/iajpiophkcdghonpijkcgpjafbcjhkko?pli=1)
|
1. 安装插件[CNB LIVE](https://chromewebstore.google.com/detail/cnb-live/iajpiophkcdghonpijkcgpjafbcjhkko?pli=1)
|
||||||
@@ -60,6 +60,9 @@ export const getLiveMdContent = (opts?: { more?: boolean }) => {
|
|||||||
4. 运行cli命令,ev cnb live -c /workspace/live/keep.json.(直接对话opencode或者openclaw调用cnb-live技能即可)
|
4. 运行cli命令,ev cnb live -c /workspace/live/keep.json.(直接对话opencode或者openclaw调用cnb-live技能即可)
|
||||||
|
|
||||||
方法2:环境变量设置CNB_COOKIE,直接opencode或者openclaw的ui界面对话说,cnb-keep-live保活,他会自动调用保活,同时不需要点cnb-lie插件获取配置。
|
方法2:环境变量设置CNB_COOKIE,直接opencode或者openclaw的ui界面对话说,cnb-keep-live保活,他会自动调用保活,同时不需要点cnb-lie插件获取配置。
|
||||||
|
|
||||||
|
2. Opencode web访问说明
|
||||||
|
Opencode打开web地址,需要在浏览器输入用户名和密码,用户名固定为root,密码为CNB_TOKEN的值. 纯连接打开包含账号密码,第一次点击后,需要把账号密码清理掉才能访问,opencode的bug导致的。
|
||||||
`
|
`
|
||||||
const labels = [
|
const labels = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { app, assistantConfig } from '@/app.ts';
|
import { app, assistantConfig } from '@/app.ts';
|
||||||
import { reload } from '../../module/reload-server.ts';
|
import { reload } from '../../module/reload-server.ts';
|
||||||
|
import { useKey } from '@kevisual/context';
|
||||||
|
import { getCNBUrl } from '@/lib.ts';
|
||||||
|
|
||||||
app
|
app
|
||||||
.route({
|
.route({
|
||||||
@@ -29,13 +31,15 @@ app
|
|||||||
app.route({
|
app.route({
|
||||||
path: 'config',
|
path: 'config',
|
||||||
key: 'getId',
|
key: 'getId',
|
||||||
description: '获取appId',
|
description: '获取appId和访问地址',
|
||||||
|
|
||||||
}).define(async (ctx) => {
|
}).define(async (ctx) => {
|
||||||
const config = assistantConfig.getCacheAssistantConfig();
|
const config = assistantConfig.getCacheAssistantConfig();
|
||||||
const appId = config?.app?.id || null;
|
const appId = config?.app?.id || null;
|
||||||
|
let kevisualUrl = getCNBUrl() || 'https://kevisual.cn';
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
id: appId,
|
id: appId,
|
||||||
|
url: kevisualUrl,
|
||||||
}
|
}
|
||||||
|
|
||||||
}).addTo(app);
|
}).addTo(app);
|
||||||
19
package.json
19
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@kevisual/cli",
|
"name": "@kevisual/cli",
|
||||||
"version": "0.1.13",
|
"version": "0.1.18",
|
||||||
"description": "envision 命令行工具",
|
"description": "envision 命令行工具",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"basename": "/root/cli",
|
"basename": "/root/cli",
|
||||||
@@ -46,30 +46,31 @@
|
|||||||
"@kevisual/auth": "^2.0.3",
|
"@kevisual/auth": "^2.0.3",
|
||||||
"@kevisual/context": "^0.0.8",
|
"@kevisual/context": "^0.0.8",
|
||||||
"@kevisual/use-config": "^1.0.30",
|
"@kevisual/use-config": "^1.0.30",
|
||||||
"@opencode-ai/sdk": "^1.2.14",
|
"@opencode-ai/sdk": "^1.2.21",
|
||||||
"@types/busboy": "^1.5.4",
|
"@types/busboy": "^1.5.4",
|
||||||
"busboy": "^1.6.0",
|
"busboy": "^1.6.0",
|
||||||
"eventemitter3": "^5.0.4",
|
"eventemitter3": "^5.0.4",
|
||||||
"jose": "^6.1.3",
|
"jose": "^6.2.0",
|
||||||
"lowdb": "^7.0.1",
|
"lowdb": "^7.0.1",
|
||||||
"lru-cache": "^11.2.6",
|
"lru-cache": "^11.2.6",
|
||||||
"micromatch": "^4.0.8",
|
"micromatch": "^4.0.8",
|
||||||
|
"nanoid": "^5.1.6",
|
||||||
"pm2": "latest",
|
"pm2": "latest",
|
||||||
"semver": "^7.7.4",
|
"semver": "^7.7.4",
|
||||||
"unstorage": "^1.17.4"
|
"unstorage": "^1.17.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@kevisual/api": "^0.0.60",
|
"@kevisual/api": "^0.0.62",
|
||||||
"@kevisual/cnb": "^0.0.32",
|
"@kevisual/cnb": "^0.0.37",
|
||||||
"@kevisual/dts": "^0.0.4",
|
"@kevisual/dts": "^0.0.4",
|
||||||
"@kevisual/load": "^0.0.6",
|
"@kevisual/load": "^0.0.6",
|
||||||
"@kevisual/logger": "^0.0.4",
|
"@kevisual/logger": "^0.0.4",
|
||||||
"@kevisual/query": "0.0.52",
|
"@kevisual/query": "0.0.53",
|
||||||
"@types/bun": "^1.3.9",
|
"@types/bun": "^1.3.10",
|
||||||
"@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": "^25.3.0",
|
"@types/node": "^25.3.5",
|
||||||
"@types/semver": "^7.7.1",
|
"@types/semver": "^7.7.1",
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
"commander": "^14.0.3",
|
"commander": "^14.0.3",
|
||||||
@@ -80,7 +81,7 @@
|
|||||||
"ignore": "^7.0.5",
|
"ignore": "^7.0.5",
|
||||||
"jsonwebtoken": "^9.0.3",
|
"jsonwebtoken": "^9.0.3",
|
||||||
"pm2": "^6.0.14",
|
"pm2": "^6.0.14",
|
||||||
"tar": "^7.5.9",
|
"tar": "^7.5.10",
|
||||||
"zustand": "^5.0.11"
|
"zustand": "^5.0.11"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
1502
pnpm-lock.yaml
generated
1502
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -4,13 +4,15 @@ import path from 'path';
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import FormData from 'form-data';
|
import FormData from 'form-data';
|
||||||
import { getBaseURL, query, storage } from '@/module/query.ts';
|
import { getBaseURL, query, storage } from '@/module/query.ts';
|
||||||
import { input, confirm } from '@inquirer/prompts';
|
import { confirm } from '@inquirer/prompts';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { upload } from '@/module/download/upload.ts';
|
import { upload } from '@/module/download/upload.ts';
|
||||||
import { getBufferHash, getHash } from '@/uitls/hash.ts';
|
import { getHash } from '@/uitls/hash.ts';
|
||||||
import { queryAppVersion } from '@/query/app-manager/query-app.ts';
|
import { queryAppVersion } from '@/query/app-manager/query-app.ts';
|
||||||
import { logger } from '@/module/logger.ts';
|
import { logger } from '@/module/logger.ts';
|
||||||
import { getUsername } from './login.ts';
|
import { getUsername } from './login.ts';
|
||||||
|
import { customAlphabet } from 'nanoid';
|
||||||
|
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 8);
|
||||||
/**
|
/**
|
||||||
* 获取package.json 中的 basename, version, user, appKey
|
* 获取package.json 中的 basename, version, user, appKey
|
||||||
* @returns
|
* @returns
|
||||||
@@ -23,7 +25,7 @@ export const getPackageJson = (opts?: { version?: string; appKey?: string }) =>
|
|||||||
try {
|
try {
|
||||||
const packageJson = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
const packageJson = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
||||||
const basename = packageJson.basename || '';
|
const basename = packageJson.basename || '';
|
||||||
const version = packageJson.version || '';
|
const version = packageJson.version || '1.0.0';
|
||||||
const app = packageJson.app;
|
const app = packageJson.app;
|
||||||
const userAppArry = basename.split('/');
|
const userAppArry = basename.split('/');
|
||||||
if (userAppArry.length <= 2 && !opts?.appKey) {
|
if (userAppArry.length <= 2 && !opts?.appKey) {
|
||||||
@@ -41,33 +43,29 @@ const command = new Command('deploy')
|
|||||||
.argument('<filePath>', 'Path to the file to be uploaded, filepath or directory') // 定义文件路径参数
|
.argument('<filePath>', 'Path to the file to be uploaded, filepath or directory') // 定义文件路径参数
|
||||||
.option('-v, --version <version>', 'verbose')
|
.option('-v, --version <version>', 'verbose')
|
||||||
.option('-k, --key <key>', 'key')
|
.option('-k, --key <key>', 'key')
|
||||||
.option('-y, --yes <yes>', 'yes')
|
.option('-y, --yes <yes>', 'yes 已经去除')
|
||||||
.option('-o, --org <org>', 'org')
|
.option('-o, --org <org>', 'org')
|
||||||
.option('-u, --update', 'load current app. set current version in product。 redis 缓存更新')
|
.option('-u, --update', 'load current app. set current version in product。 redis 缓存更新')
|
||||||
.option('-s, --showBackend', 'show backend url, 部署的后端应用,显示执行的cli命令')
|
.option('-s, --showBackend', 'show backend url, 部署的后端应用,显示执行的cli命令')
|
||||||
.option('-d, --dot', '是否上传隐藏文件')
|
.option('-d, --dot', '是否上传隐藏文件')
|
||||||
.option('--dir, --directory <directory>', '上传的默认路径')
|
.option('--dir, --directory <directory>', '上传的prefix路径,默认为空,例如设置为static,则会上传到/${username}/resources/${key}/${version}/static/路径下')
|
||||||
.action(async (filePath, options) => {
|
.action(async (filePath, options) => {
|
||||||
try {
|
try {
|
||||||
let { version, key, yes, update, org, showBackend } = options;
|
let { version, key, update, org, showBackend } = options;
|
||||||
const dot = !!options.dot;
|
const dot = !!options.dot;
|
||||||
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 || '1.0.0';
|
||||||
}
|
}
|
||||||
if (!key && pkgInfo?.appKey) {
|
if (!key && pkgInfo?.appKey) {
|
||||||
key = pkgInfo?.appKey || '';
|
key = pkgInfo?.appKey || '';
|
||||||
}
|
}
|
||||||
logger.debug('start deploy');
|
logger.debug('start deploy');
|
||||||
if (!version) {
|
if (!version) {
|
||||||
version = await input({
|
version = '1.0.0';
|
||||||
message: 'Enter your version:',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (!key) {
|
if (!key) {
|
||||||
key = await input({
|
key = nanoid(8);
|
||||||
message: 'Enter your key:',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
const pwd = process.cwd();
|
const pwd = process.cwd();
|
||||||
const directory = path.join(pwd, filePath);
|
const directory = path.join(pwd, filePath);
|
||||||
@@ -99,15 +97,6 @@ const command = new Command('deploy')
|
|||||||
}
|
}
|
||||||
logger.debug('upload Files', _relativeFiles);
|
logger.debug('upload Files', _relativeFiles);
|
||||||
logger.debug('upload Files Key', key, version);
|
logger.debug('upload Files Key', key, version);
|
||||||
if (!yes) {
|
|
||||||
// 确认是否上传
|
|
||||||
const confirmed = await confirm({
|
|
||||||
message: 'Do you want to upload these files?',
|
|
||||||
});
|
|
||||||
if (!confirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let username = '';
|
let username = '';
|
||||||
if (pkgInfo?.user) {
|
if (pkgInfo?.user) {
|
||||||
username = pkgInfo.user;
|
username = pkgInfo.user;
|
||||||
@@ -169,7 +158,7 @@ type UploadFileOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const uploadFilesV2 = async (files: string[], directory: string, opts: UploadFileOptions): Promise<any> => {
|
export const uploadFilesV2 = async (files: string[], directory: string, opts: UploadFileOptions): Promise<any> => {
|
||||||
const { key, version, username } = opts || {};
|
const { key, version, username, directory: prefix } = opts || {};
|
||||||
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
const file = files[i];
|
const file = files[i];
|
||||||
@@ -184,7 +173,7 @@ export const uploadFilesV2 = async (files: string[], directory: string, opts: Up
|
|||||||
filepath: file,
|
filepath: file,
|
||||||
});
|
});
|
||||||
const _baseURL = getBaseURL();
|
const _baseURL = getBaseURL();
|
||||||
const url = new URL(`/${username}/resources/${key}/${version}/${file}`, _baseURL);
|
const url = new URL(`/${username}/resources/${key}/${version}/${prefix ? prefix + '/' : ''}${file}`, _baseURL);
|
||||||
// console.log('upload file', file, filePath);
|
// console.log('upload file', file, filePath);
|
||||||
const token = await storage.getItem('token');
|
const token = await storage.getItem('token');
|
||||||
const check = () => {
|
const check = () => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { program as app, Command } from '@/program.ts';
|
|||||||
import { getConfig, getEnvToken, writeConfig } from '@/module/index.ts';
|
import { getConfig, getEnvToken, writeConfig } from '@/module/index.ts';
|
||||||
import { queryLogin, storage } from '@/module/query.ts';
|
import { queryLogin, storage } from '@/module/query.ts';
|
||||||
import { input } from '@inquirer/prompts';
|
import { input } from '@inquirer/prompts';
|
||||||
import util from 'util';
|
import { Kevisual } from '@/module/kevisual.ts';
|
||||||
function isNumeric(str: string) {
|
function isNumeric(str: string) {
|
||||||
return /^-?\d+\.?\d*$/.test(str);
|
return /^-?\d+\.?\d*$/.test(str);
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,22 @@ const tokenList = new Command('list')
|
|||||||
console.log(queryLogin.cacheStore.cacheData);
|
console.log(queryLogin.cacheStore.cacheData);
|
||||||
// console.log(util.inspect(res, { colors: true, depth: 4 }));
|
// console.log(util.inspect(res, { colors: true, depth: 4 }));
|
||||||
});
|
});
|
||||||
|
|
||||||
token.addCommand(tokenList);
|
token.addCommand(tokenList);
|
||||||
|
|
||||||
|
const createToken = new Command('create')
|
||||||
|
.description('create jwks token')
|
||||||
|
.action(async (opts) => {
|
||||||
|
const kevisual = new Kevisual();
|
||||||
|
const res = await kevisual.getAdminToken();
|
||||||
|
if (res.code === 200) {
|
||||||
|
const jwtToken = res.data?.accessToken;
|
||||||
|
console.log('============jwt token============\n\n');
|
||||||
|
console.log(jwtToken);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
token.addCommand(createToken);
|
||||||
app.addCommand(token);
|
app.addCommand(token);
|
||||||
|
|
||||||
const baseURL = new Command('baseURL')
|
const baseURL = new Command('baseURL')
|
||||||
|
|||||||
@@ -103,17 +103,23 @@ export class SyncBase {
|
|||||||
* @param opts.getFile 是否检测文件是否存在
|
* @param opts.getFile 是否检测文件是否存在
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async getSyncList(opts?: { getFile?: boolean }): Promise<SyncList[]> {
|
async getSyncList(opts?: { getFile?: boolean, getLocalFile?: boolean }): Promise<SyncList[]> {
|
||||||
const config = this.config!;
|
const config = this.config!;
|
||||||
let sync = config?.sync || {};
|
let sync = config?.sync || {};
|
||||||
|
const local = opts?.getLocalFile ?? true;
|
||||||
const syncDirectory = await this.getSyncDirectoryList();
|
const syncDirectory = await this.getSyncDirectoryList();
|
||||||
|
if (local) {
|
||||||
sync = this.getMergeSync(sync, syncDirectory.sync);
|
sync = this.getMergeSync(sync, syncDirectory.sync);
|
||||||
|
}
|
||||||
const syncKeys = Object.keys(sync);
|
const syncKeys = Object.keys(sync);
|
||||||
const baseURL = this.baseURL;
|
const baseURL = this.baseURL;
|
||||||
const syncList = syncKeys.map((key) => {
|
const syncList = syncKeys.map((key) => {
|
||||||
const value = sync[key];
|
const value = sync[key];
|
||||||
const filepath = path.join(this.#dir, key); // 文件的路径
|
const filepath = path.join(this.#dir, key); // 文件的路径
|
||||||
if (filepath.includes('node_modules') || filepath.includes('.git')) {
|
const dirs = path.dirname(filepath).split(path.sep);
|
||||||
|
const hasGit = dirs.includes('.git');
|
||||||
|
const hasNodeModules = dirs.includes('node_modules');
|
||||||
|
if (hasGit || hasNodeModules) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
|
|||||||
@@ -172,11 +172,16 @@ 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 优先级更高')
|
.option('-i --link <link>', '克隆链接, 比 kevisual.json 优先级更高')
|
||||||
|
.option('-l --local', '值对sync的列表进行clone处理,只对sync列表处理')
|
||||||
.description('检查目录')
|
.description('检查目录')
|
||||||
.action(async (opts) => {
|
.action(async (opts) => {
|
||||||
const link = opts.link || '';
|
let link = opts.link || '';
|
||||||
|
const local = opts.local || false;
|
||||||
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) {
|
if (link) {
|
||||||
|
if (!link.endsWith('.json')) {
|
||||||
|
link = link + (link.endsWith('/') ? '' : '/') + 'kevisual.json';
|
||||||
|
}
|
||||||
const res = await query.fetchText(link);
|
const res = await query.fetchText(link);
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
fs.writeFileSync(sync.configPath, JSON.stringify(res.data, null, 2));
|
fs.writeFileSync(sync.configPath, JSON.stringify(res.data, null, 2));
|
||||||
@@ -186,7 +191,7 @@ const clone = new Command('clone')
|
|||||||
}
|
}
|
||||||
await sync.init()
|
await sync.init()
|
||||||
}
|
}
|
||||||
const syncList = await sync.getSyncList();
|
const syncList = await sync.getSyncList({ getLocalFile: !local });
|
||||||
logger.debug(syncList);
|
logger.debug(syncList);
|
||||||
logger.info('检查目录\n');
|
logger.info('检查目录\n');
|
||||||
const checkList = await sync.getCheckList();
|
const checkList = await sync.getCheckList();
|
||||||
@@ -211,24 +216,23 @@ const clone = new Command('clone')
|
|||||||
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);
|
||||||
for (const matchItem of matchList) {
|
for (const matchItem of matchList) {
|
||||||
if (!matchItem) continue;
|
if (!matchItem) continue;
|
||||||
|
if (local) {
|
||||||
|
const some = syncList.some((syncItem) => {
|
||||||
|
if (syncItem.url === matchItem.url) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (!some) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
let needDownload = true;
|
let needDownload = true;
|
||||||
let hash = '';
|
let hash = '';
|
||||||
await sync.getDir(matchItem.absolute, true);
|
await sync.getDir(matchItem.absolute, true);
|
||||||
|
|||||||
19
src/module/kevisual.ts
Normal file
19
src/module/kevisual.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { query } from './query.ts';
|
||||||
|
import { Query } from '@kevisual/query';
|
||||||
|
|
||||||
|
export class Kevisual {
|
||||||
|
query: Query;
|
||||||
|
constructor() {
|
||||||
|
this.query = query;
|
||||||
|
}
|
||||||
|
getAdminToken() {
|
||||||
|
const res = this.query.post({
|
||||||
|
path: 'user',
|
||||||
|
key: 'token-create',
|
||||||
|
payload: {
|
||||||
|
loginType: 'jwks',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user