Compare commits

...

12 Commits

Author SHA1 Message Date
94e4db8d44 feat: 更新版本号至 0.1.18 2026-03-08 14:08:22 +08:00
cb8e709c5d fix(deploy): set default version to 1.0.0 and generate key using nanoid; remove confirmation prompt 2026-03-08 14:07:18 +08:00
8867951893 Refactor code structure for improved readability and maintainability 2026-03-06 02:12:01 +08:00
2d4666ce90 feat: 添加 Kevisual 类以获取管理员令牌;更新 ls-token 命令以支持创建 jwks 令牌 2026-03-06 02:11:05 +08:00
ad494248cc feat: 更新 RemoteApp 初始化逻辑,添加 token 参数;更新版本号至 0.1.16 2026-03-05 04:16:08 +08:00
5594123cc7 feat: 更新 RemoteApp 类,增加 isVerified 属性及验证等待逻辑;优化错误日志输出 2026-03-05 04:15:04 +08:00
3b383639d6 feat: 增强 RemoteApp 支持用户名;更新 light-code 路由处理逻辑;优化 SyncBase 文件路径检查 2026-03-05 02:45:51 +08:00
c2c6d4a7d3 feat: 更新版本号至 0.1.15;增强 getSyncList 方法以支持本地文件选项 2026-03-03 00:40:30 +08:00
57bf884360 feat: 更新上传文件功能,增强目录路径处理逻辑 2026-03-02 02:06:16 +08:00
73d0fb9730 feat: 更新获取配置接口,增加动态获取访问地址功能 2026-02-28 04:33:24 +08:00
acd88fb815 feat: 更新保活说明,增强对 opencode web 访问的说明 2026-02-27 23:47:39 +08:00
e5e04e0cc9 feat: 更新版本号至 0.1.14 2026-02-27 01:43:53 +08:00
14 changed files with 931 additions and 813 deletions

View File

@@ -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",

View File

@@ -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
}

View File

@@ -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 内置的自动重连机制

View File

@@ -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,

View File

@@ -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);

View File

@@ -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 = [
{ {

View File

@@ -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);

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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 = () => {

View File

@@ -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')

View File

@@ -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();
sync = this.getMergeSync(sync, syncDirectory.sync); if (local) {
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') {

View File

@@ -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
View 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;
}
}