feat: add user info command and package management for assistant app
- Implemented a new command 'me' to view current user information in the assistant application. - Created a common configuration file for the assistant app with package details and scripts. - Added functionality to check and update package.json dependencies and devDependencies in the assistant app. - Refactored storage initialization in query module to use StorageNode.
This commit is contained in:
@@ -3,30 +3,7 @@
|
|||||||
"metadata": {
|
"metadata": {
|
||||||
"share": "public"
|
"share": "public"
|
||||||
},
|
},
|
||||||
"checkDir": {
|
"checkDir": {},
|
||||||
"src/query/query-login": {
|
"syncDirectory": [],
|
||||||
"url": "https://kevisual.xiongxiao.me/root/ai/code/registry/query/query-login",
|
"sync": {}
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"src/query/query-ai": {
|
|
||||||
"url": "https://kevisual.xiongxiao.me/root/ai/code/registry/query/query-ai",
|
|
||||||
"enabled": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"syncDirectory": [
|
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
"src/query/query-login/**/*",
|
|
||||||
"src/query/query-ai/**/*"
|
|
||||||
],
|
|
||||||
"ignore": [],
|
|
||||||
"registry": "https://kevisual.xiongxiao.me/root/ai/code/registry",
|
|
||||||
"replace": {
|
|
||||||
"src/": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sync": {
|
|
||||||
"src/query/index.ts": "https://kevisual.xiongxiao.me/root/ai/code/registry/query/index.ts"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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.0",
|
"packageManager": "pnpm@10.30.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
@@ -44,17 +44,17 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@inquirer/prompts": "^8.2.1",
|
"@inquirer/prompts": "^8.2.1",
|
||||||
"@kevisual/ai": "^0.0.24",
|
"@kevisual/ai": "^0.0.24",
|
||||||
"@kevisual/api": "^0.0.52",
|
"@kevisual/api": "^0.0.55",
|
||||||
"@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.49",
|
"@kevisual/query": "0.0.49",
|
||||||
"@kevisual/router": "^0.0.80",
|
"@kevisual/router": "^0.0.83",
|
||||||
"@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.6",
|
"@opencode-ai/plugin": "^1.2.10",
|
||||||
"@types/bun": "^1.3.9",
|
"@types/bun": "^1.3.9",
|
||||||
"@types/node": "^25.2.3",
|
"@types/node": "^25.3.0",
|
||||||
"@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,11 +76,11 @@
|
|||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.992.0",
|
"@aws-sdk/client-s3": "^3.994.0",
|
||||||
"@kevisual/js-filter": "^0.0.5",
|
"@kevisual/js-filter": "^0.0.5",
|
||||||
"@kevisual/oss": "^0.0.19",
|
"@kevisual/oss": "^0.0.19",
|
||||||
"@kevisual/video-tools": "^0.0.13",
|
"@kevisual/video-tools": "^0.0.13",
|
||||||
"@opencode-ai/sdk": "^1.2.6",
|
"@opencode-ai/sdk": "^1.2.10",
|
||||||
"es-toolkit": "^1.44.0",
|
"es-toolkit": "^1.44.0",
|
||||||
"eventemitter3": "^5.0.4",
|
"eventemitter3": "^5.0.4",
|
||||||
"lowdb": "^7.0.1",
|
"lowdb": "^7.0.1",
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { program, Command, assistantConfig } from '@/program.ts';
|
import { program, Command, assistantConfig } from '@/program.ts';
|
||||||
import { spawnSync } from 'node:child_process';
|
|
||||||
import { parseHomeArg, HomeConfigDir } from '@/module/assistant/config/args.ts';
|
import { parseHomeArg, HomeConfigDir } from '@/module/assistant/config/args.ts';
|
||||||
import { execCommand } from '@/module/npm-install.ts';
|
import { execCommand } from '@/module/npm-install.ts';
|
||||||
|
|
||||||
|
|||||||
14
assistant/src/command/user/me.ts
Normal file
14
assistant/src/command/user/me.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { program, Command, assistantConfig } from '@/program.ts';
|
||||||
|
import { AssistantQuery } from '@/lib.ts';
|
||||||
|
import { logger } from '@/module/logger.ts';
|
||||||
|
|
||||||
|
const me = new Command('me')
|
||||||
|
.description('查看当前用户信息')
|
||||||
|
.action(async () => {
|
||||||
|
const aq = new AssistantQuery(assistantConfig);
|
||||||
|
await aq.init()
|
||||||
|
const info = await aq.queryLogin.checkLocalUser()
|
||||||
|
logger.info(info);
|
||||||
|
});
|
||||||
|
|
||||||
|
program.addCommand(me);
|
||||||
@@ -6,6 +6,7 @@ import './command/app/index.ts';
|
|||||||
import './command/run-scripts/index.ts';
|
import './command/run-scripts/index.ts';
|
||||||
import './command/ai/index.ts';
|
import './command/ai/index.ts';
|
||||||
import './command/plugins/install.ts';
|
import './command/plugins/install.ts';
|
||||||
|
import './command/user/me.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过命令行解析器解析参数
|
* 通过命令行解析器解析参数
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import { useKey } from '@kevisual/use-config';
|
|
||||||
export const getFileConfig = (filePath: string): any => {
|
export const getFileConfig = (filePath: string): any => {
|
||||||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,11 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { homedir } from 'os';
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { checkFileExists, createDir } from '../file/index.ts';
|
import { checkFileExists, createDir } from '../file/index.ts';
|
||||||
import { ProxyInfo } from '../proxy/proxy.ts';
|
import { ProxyInfo } from '../proxy/proxy.ts';
|
||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import { logger } from '@/module/logger.ts';
|
|
||||||
import { z } from 'zod'
|
|
||||||
import { HomeConfigDir } from './args.ts'
|
import { HomeConfigDir } from './args.ts'
|
||||||
import { getFileConfig } from './get-assistan-config.ts';
|
import { getFileConfig } from './get-assistan-config.ts';
|
||||||
import { useKey } from '@kevisual/use-config';
|
import { useKey } from '@kevisual/use-config';
|
||||||
import { env } from 'pm2';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 助手配置文件路径, 全局配置文件目录
|
* 助手配置文件路径, 全局配置文件目录
|
||||||
@@ -231,25 +227,15 @@ export class AssistantConfig {
|
|||||||
getConfig(): AssistantConfigData {
|
getConfig(): AssistantConfigData {
|
||||||
try {
|
try {
|
||||||
if (!checkFileExists(this.configPath.configPath)) {
|
if (!checkFileExists(this.configPath.configPath)) {
|
||||||
fs.writeFileSync(this.configPath.configPath, JSON.stringify({ proxy: [] }, null, 2));
|
const defaultConfig = this.getDefaultInitAssistantConfig();
|
||||||
return {
|
fs.writeFileSync(this.configPath.configPath, JSON.stringify(defaultConfig, null, 2));
|
||||||
app: {
|
return defaultConfig;
|
||||||
url: 'https://kevisual.cn',
|
|
||||||
},
|
|
||||||
proxy: [],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
assistantConfig = getFileConfig(this.configPath.configPath);
|
assistantConfig = getFileConfig(this.configPath.configPath);
|
||||||
return assistantConfig;
|
return assistantConfig;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('file read', error.message);
|
console.error('file read', error.message);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
return {
|
|
||||||
app: {
|
|
||||||
url: 'https://kevisual.cn',
|
|
||||||
},
|
|
||||||
proxy: [],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getCacheAssistantConfig() {
|
getCacheAssistantConfig() {
|
||||||
@@ -393,6 +379,23 @@ export class AssistantConfig {
|
|||||||
// 如果没有找到助手配置文件目录,则返回当前目录, 执行默认创建助手配置文件目录
|
// 如果没有找到助手配置文件目录,则返回当前目录, 执行默认创建助手配置文件目录
|
||||||
return checkConfigDir;
|
return checkConfigDir;
|
||||||
}
|
}
|
||||||
|
protected getDefaultInitAssistantConfig() {
|
||||||
|
const id = randomId();
|
||||||
|
return {
|
||||||
|
app: {
|
||||||
|
url: 'https://kevisual.cn',
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
description: '助手配置文件',
|
||||||
|
docs: "https://kevisual.cn/root/cli/docs/",
|
||||||
|
home: '/root/home',
|
||||||
|
proxy: [],
|
||||||
|
share: {
|
||||||
|
enabled: false,
|
||||||
|
url: 'https://kevisual.cn/ws/proxy',
|
||||||
|
},
|
||||||
|
} as AssistantConfigData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppConfig = {
|
type AppConfig = {
|
||||||
@@ -408,4 +411,6 @@ export const parseIfJson = (content: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export * from './args.ts';
|
export * from './args.ts';
|
||||||
|
|
||||||
|
const randomId = () => Math.random().toString(36).substring(2, 8);
|
||||||
@@ -216,7 +216,7 @@ export class AssistantApp extends Manager {
|
|||||||
const routeStr = typeof route === 'string' ? route : route.path;
|
const routeStr = typeof route === 'string' ? route : route.path;
|
||||||
const resolvedPath = this.resolver.resolve(routeStr);
|
const resolvedPath = this.resolver.resolve(routeStr);
|
||||||
await import(resolvedPath);
|
await import(resolvedPath);
|
||||||
console.log('[routes] 路由已初始化', route, resolvedPath);
|
console.log('[routes] 路由已初始化', route);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('初始化路由失败', route, err);
|
console.error('初始化路由失败', route, err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,33 @@
|
|||||||
import { DataOpts, Query } from "@kevisual/query/query";
|
import { DataOpts, Query } from "@kevisual/query/query";
|
||||||
import { AssistantConfig } from "../config/index.ts";
|
import { AssistantConfig } from "../config/index.ts";
|
||||||
|
import { QueryLoginNode } from '@kevisual/api/query-login-node'
|
||||||
|
import { EventEmitter } from 'eventemitter3'
|
||||||
|
|
||||||
export class AssistantQuery {
|
export class AssistantQuery {
|
||||||
config: AssistantConfig;
|
config: AssistantConfig;
|
||||||
query: Query ;
|
query: Query;
|
||||||
|
queryLogin: QueryLoginNode;
|
||||||
|
emitter = new EventEmitter();
|
||||||
|
isLoad = false;
|
||||||
constructor(config: AssistantConfig) {
|
constructor(config: AssistantConfig) {
|
||||||
config.checkMounted();
|
config.checkMounted();
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.query = new Query({ url: config.getRegistry() + '/api/router' });
|
this.query = new Query({ url: config.getRegistry() + '/api/router' });
|
||||||
|
this.queryLogin = new QueryLoginNode({
|
||||||
|
query: this.query, onLoad: () => {
|
||||||
|
this.isLoad = true;
|
||||||
|
this.emitter.emit('load')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async init() {
|
||||||
|
if (this.isLoad) return true;
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.emitter.on('load', () => {
|
||||||
|
this.isLoad = true;
|
||||||
|
resolve(true);
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
post(body: any, options?: DataOpts) {
|
post(body: any, options?: DataOpts) {
|
||||||
return this.query.post(body, options);
|
return this.query.post(body, options);
|
||||||
@@ -16,4 +35,7 @@ export class AssistantQuery {
|
|||||||
get(body: any, options?: DataOpts) {
|
get(body: any, options?: DataOpts) {
|
||||||
return this.query.get(body, options);
|
return this.query.get(body, options);
|
||||||
}
|
}
|
||||||
|
getToken() {
|
||||||
|
return this.queryLogin.getToken();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -29,9 +29,9 @@ export const installDeps = async (opts: InstallDepsOptions) => {
|
|||||||
console.log('installDeps', appPath, params);
|
console.log('installDeps', appPath, params);
|
||||||
const syncSpawn = opts.sync ? spawnSync : spawn;
|
const syncSpawn = opts.sync ? spawnSync : spawn;
|
||||||
if (isPnpm) {
|
if (isPnpm) {
|
||||||
syncSpawn('pnpm', params, { cwd: appPath, stdio: 'inherit', env: process.env });
|
syncSpawn('pnpm', params, { cwd: appPath, stdio: 'inherit', env: { ...process.env, CI: 'true' }, shell: true });
|
||||||
} else {
|
} else {
|
||||||
syncSpawn('npm', params, { cwd: appPath, stdio: 'inherit', env: process.env });
|
syncSpawn('npm', params, { cwd: appPath, stdio: 'inherit', env: process.env, shell: true });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import { Query } from '@kevisual/query';
|
|
||||||
|
|
||||||
export const query = new Query();
|
|
||||||
|
|
||||||
export const clientQuery = new Query({ url: '/client/router' });
|
|
||||||
|
|
||||||
export { QueryUtil } from '@kevisual/router/define';
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import { QueryUtil } from '@/query/index.ts';
|
|
||||||
|
|
||||||
type Message = {
|
|
||||||
role?: 'user' | 'assistant' | 'system' | 'tool';
|
|
||||||
content?: string;
|
|
||||||
name?: string;
|
|
||||||
};
|
|
||||||
export type PostChat = {
|
|
||||||
messages?: Message[];
|
|
||||||
model?: string;
|
|
||||||
group?: string;
|
|
||||||
user?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ChatDataOpts = {
|
|
||||||
id?: string;
|
|
||||||
title?: string;
|
|
||||||
messages?: any[];
|
|
||||||
data?: any;
|
|
||||||
type?: 'temp' | 'keep' | string;
|
|
||||||
};
|
|
||||||
export type ChatOpts = {
|
|
||||||
username: string;
|
|
||||||
model: string;
|
|
||||||
/**
|
|
||||||
* 获取完整消息回复
|
|
||||||
*/
|
|
||||||
getFull?: boolean;
|
|
||||||
group: string;
|
|
||||||
/**
|
|
||||||
* openai的参数
|
|
||||||
*/
|
|
||||||
options?: any;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const appDefine = QueryUtil.create({
|
|
||||||
chat: {
|
|
||||||
path: 'ai',
|
|
||||||
key: 'chat',
|
|
||||||
description: '与 AI 进行对话, 调用 GPT 的AI 服务,生成结果,并返回。',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
import { appDefine } from './defines/ai.ts';
|
|
||||||
import { PostChat, ChatOpts, ChatDataOpts } from './defines/ai.ts';
|
|
||||||
|
|
||||||
import { BaseQuery, DataOpts, Query } from '@kevisual/query/query';
|
|
||||||
|
|
||||||
export { appDefine };
|
|
||||||
|
|
||||||
export class QueryApp<T extends Query = Query> extends BaseQuery<T, typeof appDefine> {
|
|
||||||
constructor(opts?: { query: T }) {
|
|
||||||
super({
|
|
||||||
...opts,
|
|
||||||
query: opts?.query!,
|
|
||||||
queryDefine: appDefine,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 与 AI 进行对话, 调用 GPT 的AI 服务,生成结果,并返回。
|
|
||||||
* @param data
|
|
||||||
* @param opts
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
postChat(data: PostChat, opts?: DataOpts) {
|
|
||||||
return this.chain('chat').post(data, opts);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 获取模型列表
|
|
||||||
* @param opts
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
getModelList(data?: { usernames?: string[] }, opts?: DataOpts) {
|
|
||||||
return this.query.post(
|
|
||||||
{
|
|
||||||
path: 'ai',
|
|
||||||
key: 'get-model-list',
|
|
||||||
data,
|
|
||||||
},
|
|
||||||
opts,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 聊天对话模型
|
|
||||||
* @param data
|
|
||||||
* @param chatOpts
|
|
||||||
* @param opts
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
chat(data: ChatDataOpts, chatOpts: ChatOpts, opts?: DataOpts) {
|
|
||||||
const { username, model, group, getFull = true } = chatOpts;
|
|
||||||
if (!username || !model || !group) {
|
|
||||||
throw new Error('username, model, group is required');
|
|
||||||
}
|
|
||||||
return this.query.post(
|
|
||||||
{
|
|
||||||
path: 'ai',
|
|
||||||
key: 'chat',
|
|
||||||
...chatOpts,
|
|
||||||
getFull,
|
|
||||||
data,
|
|
||||||
},
|
|
||||||
opts,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
clearConfigCache(opts?: DataOpts) {
|
|
||||||
return this.query.post(
|
|
||||||
{
|
|
||||||
path: 'ai',
|
|
||||||
key: 'clear-cache',
|
|
||||||
},
|
|
||||||
opts,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 获取聊天使用情况
|
|
||||||
* @param opts
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
getChatUsage(opts?: DataOpts) {
|
|
||||||
return this.query.post(
|
|
||||||
{
|
|
||||||
path: 'ai',
|
|
||||||
key: 'get-chat-usage',
|
|
||||||
},
|
|
||||||
opts,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除当前用户模型自己的统计
|
|
||||||
* @param opts
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
clearSelfUsage(opts?: DataOpts) {
|
|
||||||
return this.query.post(
|
|
||||||
{
|
|
||||||
path: 'ai',
|
|
||||||
key: 'clear-chat-limit',
|
|
||||||
},
|
|
||||||
opts,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
export interface Cache {
|
|
||||||
/**
|
|
||||||
* @update 获取缓存
|
|
||||||
*/
|
|
||||||
get(key: string): Promise<any>;
|
|
||||||
/**
|
|
||||||
* @update 设置缓存
|
|
||||||
*/
|
|
||||||
set(key: string, value: any): Promise<any>;
|
|
||||||
/**
|
|
||||||
* @update 删除缓存
|
|
||||||
*/
|
|
||||||
del(): Promise<void>;
|
|
||||||
/**
|
|
||||||
* 初始化
|
|
||||||
*/
|
|
||||||
init?: () => Promise<any>;
|
|
||||||
}
|
|
||||||
type User = {
|
|
||||||
avatar?: string;
|
|
||||||
description?: string;
|
|
||||||
id?: string;
|
|
||||||
needChangePassword?: boolean;
|
|
||||||
orgs?: string[];
|
|
||||||
type?: string;
|
|
||||||
username?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CacheLoginUser = {
|
|
||||||
user?: User;
|
|
||||||
id?: string;
|
|
||||||
accessToken?: string;
|
|
||||||
refreshToken?: string;
|
|
||||||
};
|
|
||||||
type CacheLogin = {
|
|
||||||
loginUsers: CacheLoginUser[];
|
|
||||||
} & CacheLoginUser;
|
|
||||||
|
|
||||||
export type CacheStore<T = Cache> = {
|
|
||||||
name: string;
|
|
||||||
/**
|
|
||||||
* 缓存数据
|
|
||||||
* @important 需要先调用init
|
|
||||||
*/
|
|
||||||
cacheData: CacheLogin;
|
|
||||||
/**
|
|
||||||
* 实际操作的cache, 需要先调用init
|
|
||||||
*/
|
|
||||||
cache: T;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置当前用户
|
|
||||||
*/
|
|
||||||
setLoginUser(user: CacheLoginUser): Promise<void>;
|
|
||||||
/**
|
|
||||||
* 获取当前用户
|
|
||||||
*/
|
|
||||||
getCurrentUser(): Promise<User>;
|
|
||||||
/**
|
|
||||||
* 获取当前用户列表
|
|
||||||
*/
|
|
||||||
getCurrentUserList(): Promise<CacheLoginUser[]>;
|
|
||||||
/**
|
|
||||||
* 获取缓存的refreshToken
|
|
||||||
*/
|
|
||||||
getRefreshToken(): Promise<string>;
|
|
||||||
/**
|
|
||||||
* 获取缓存的accessToken
|
|
||||||
*/
|
|
||||||
getAccessToken(): Promise<string>;
|
|
||||||
/**
|
|
||||||
* 清除当前用户
|
|
||||||
*/
|
|
||||||
clearCurrentUser(): Promise<void>;
|
|
||||||
/**
|
|
||||||
* 清除所有用户
|
|
||||||
*/
|
|
||||||
clearAll(): Promise<void>;
|
|
||||||
|
|
||||||
getValue(): Promise<CacheLogin>;
|
|
||||||
setValue(value: CacheLogin): Promise<CacheLogin>;
|
|
||||||
delValue(): Promise<void>;
|
|
||||||
init(): Promise<any>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type LoginCacheStoreOpts = {
|
|
||||||
name: string;
|
|
||||||
cache: Cache;
|
|
||||||
};
|
|
||||||
export class LoginCacheStore implements CacheStore<any> {
|
|
||||||
cache: Cache;
|
|
||||||
name: string;
|
|
||||||
cacheData: CacheLogin;
|
|
||||||
constructor(opts: LoginCacheStoreOpts) {
|
|
||||||
if (!opts.cache) {
|
|
||||||
throw new Error('cache is required');
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
this.cache = opts.cache;
|
|
||||||
this.cacheData = {
|
|
||||||
loginUsers: [],
|
|
||||||
user: undefined,
|
|
||||||
id: undefined,
|
|
||||||
accessToken: undefined,
|
|
||||||
refreshToken: undefined,
|
|
||||||
};
|
|
||||||
this.name = opts.name;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 设置缓存
|
|
||||||
* @param key
|
|
||||||
* @param value
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async setValue(value: CacheLogin) {
|
|
||||||
await this.cache.set(this.name, value);
|
|
||||||
this.cacheData = value;
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 删除缓存
|
|
||||||
*/
|
|
||||||
async delValue() {
|
|
||||||
await this.cache.del();
|
|
||||||
}
|
|
||||||
getValue(): Promise<CacheLogin> {
|
|
||||||
return this.cache.get(this.name);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 初始化,设置默认值
|
|
||||||
*/
|
|
||||||
async init() {
|
|
||||||
const defaultData = {
|
|
||||||
loginUsers: [],
|
|
||||||
user: null,
|
|
||||||
id: null,
|
|
||||||
accessToken: null,
|
|
||||||
refreshToken: null,
|
|
||||||
};
|
|
||||||
if (this.cache.init) {
|
|
||||||
try {
|
|
||||||
const cacheData = await this.cache.init();
|
|
||||||
this.cacheData = cacheData || defaultData;
|
|
||||||
} catch (error) {
|
|
||||||
console.log('cacheInit error', error);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.cacheData = (await this.getValue()) || defaultData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 设置当前用户
|
|
||||||
* @param user
|
|
||||||
*/
|
|
||||||
async setLoginUser(user: CacheLoginUser) {
|
|
||||||
const has = this.cacheData.loginUsers.find((u) => u.id === user.id);
|
|
||||||
if (has) {
|
|
||||||
this.cacheData.loginUsers = this.cacheData?.loginUsers?.filter((u) => u?.id && u.id !== user.id);
|
|
||||||
}
|
|
||||||
this.cacheData.loginUsers.push(user);
|
|
||||||
this.cacheData.user = user.user;
|
|
||||||
this.cacheData.id = user.id;
|
|
||||||
this.cacheData.accessToken = user.accessToken;
|
|
||||||
this.cacheData.refreshToken = user.refreshToken;
|
|
||||||
await this.setValue(this.cacheData);
|
|
||||||
}
|
|
||||||
|
|
||||||
getCurrentUser(): Promise<CacheLoginUser> {
|
|
||||||
const cacheData = this.cacheData;
|
|
||||||
return Promise.resolve(cacheData.user!);
|
|
||||||
}
|
|
||||||
getCurrentUserList(): Promise<CacheLoginUser[]> {
|
|
||||||
return Promise.resolve(this.cacheData.loginUsers.filter((u) => u?.id));
|
|
||||||
}
|
|
||||||
getRefreshToken(): Promise<string> {
|
|
||||||
const cacheData = this.cacheData;
|
|
||||||
return Promise.resolve(cacheData.refreshToken || '');
|
|
||||||
}
|
|
||||||
getAccessToken(): Promise<string> {
|
|
||||||
const cacheData = this.cacheData;
|
|
||||||
return Promise.resolve(cacheData.accessToken || '');
|
|
||||||
}
|
|
||||||
|
|
||||||
async clearCurrentUser() {
|
|
||||||
const user = await this.getCurrentUser();
|
|
||||||
const has = this.cacheData.loginUsers.find((u) => u.id === user.id);
|
|
||||||
if (has) {
|
|
||||||
this.cacheData.loginUsers = this.cacheData?.loginUsers?.filter((u) => u?.id && u.id !== user.id);
|
|
||||||
}
|
|
||||||
this.cacheData.user = undefined;
|
|
||||||
this.cacheData.id = undefined;
|
|
||||||
this.cacheData.accessToken = undefined;
|
|
||||||
this.cacheData.refreshToken = undefined;
|
|
||||||
await this.setValue(this.cacheData);
|
|
||||||
}
|
|
||||||
async clearAll() {
|
|
||||||
this.cacheData.loginUsers = [];
|
|
||||||
this.cacheData.user = undefined;
|
|
||||||
this.cacheData.id = undefined;
|
|
||||||
this.cacheData.accessToken = undefined;
|
|
||||||
this.cacheData.refreshToken = undefined;
|
|
||||||
await this.setValue(this.cacheData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
import { Cache } from './login-cache.ts';
|
|
||||||
import { homedir } from 'node:os';
|
|
||||||
import { join, dirname } from 'node:path';
|
|
||||||
import fs from 'node:fs';
|
|
||||||
import { readFileSync, writeFileSync, accessSync } from 'node:fs';
|
|
||||||
import { readFile, writeFile, unlink, mkdir } from 'node:fs/promises';
|
|
||||||
export const fileExists = async (
|
|
||||||
filePath: string,
|
|
||||||
{ createIfNotExists = true, isFile = true, isDir = false }: { createIfNotExists?: boolean; isFile?: boolean; isDir?: boolean } = {},
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
accessSync(filePath, fs.constants.F_OK);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
if (createIfNotExists && isDir) {
|
|
||||||
await mkdir(filePath, { recursive: true });
|
|
||||||
return true;
|
|
||||||
} else if (createIfNotExists && isFile) {
|
|
||||||
await mkdir(dirname(filePath), { recursive: true });
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
export const readConfigFile = (filePath: string) => {
|
|
||||||
try {
|
|
||||||
const data = readFileSync(filePath, 'utf-8');
|
|
||||||
const jsonData = JSON.parse(data);
|
|
||||||
return jsonData;
|
|
||||||
} catch (error) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
export const writeConfigFile = (filePath: string, data: any) => {
|
|
||||||
writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
||||||
};
|
|
||||||
export const getHostName = () => {
|
|
||||||
const configDir = join(homedir(), '.config', 'envision');
|
|
||||||
const configFile = join(configDir, 'config.json');
|
|
||||||
const config = readConfigFile(configFile);
|
|
||||||
const baseURL = config.baseURL || 'https://kevisual.cn';
|
|
||||||
const hostname = new URL(baseURL).hostname;
|
|
||||||
return hostname;
|
|
||||||
};
|
|
||||||
export class StorageNode implements Storage {
|
|
||||||
cacheData: any;
|
|
||||||
filePath: string;
|
|
||||||
constructor() {
|
|
||||||
this.cacheData = {};
|
|
||||||
const configDir = join(homedir(), '.config', 'envision');
|
|
||||||
const hostname = getHostName();
|
|
||||||
this.filePath = join(configDir, 'config', `${hostname}-storage.json`);
|
|
||||||
fileExists(this.filePath, { isFile: true });
|
|
||||||
}
|
|
||||||
async loadCache() {
|
|
||||||
const filePath = this.filePath;
|
|
||||||
try {
|
|
||||||
const data = await readConfigFile(filePath);
|
|
||||||
this.cacheData = data;
|
|
||||||
} catch (error) {
|
|
||||||
this.cacheData = {};
|
|
||||||
await writeFile(filePath, JSON.stringify(this.cacheData, null, 2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
get length() {
|
|
||||||
return Object.keys(this.cacheData).length;
|
|
||||||
}
|
|
||||||
getItem(key: string) {
|
|
||||||
return this.cacheData[key];
|
|
||||||
}
|
|
||||||
setItem(key: string, value: any) {
|
|
||||||
this.cacheData[key] = value;
|
|
||||||
writeFile(this.filePath, JSON.stringify(this.cacheData, null, 2));
|
|
||||||
}
|
|
||||||
removeItem(key: string) {
|
|
||||||
delete this.cacheData[key];
|
|
||||||
writeFile(this.filePath, JSON.stringify(this.cacheData, null, 2));
|
|
||||||
}
|
|
||||||
clear() {
|
|
||||||
this.cacheData = {};
|
|
||||||
writeFile(this.filePath, JSON.stringify(this.cacheData, null, 2));
|
|
||||||
}
|
|
||||||
key(index: number) {
|
|
||||||
return Object.keys(this.cacheData)[index];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export class LoginNodeCache implements Cache {
|
|
||||||
filepath: string;
|
|
||||||
|
|
||||||
constructor(filepath?: string) {
|
|
||||||
this.filepath = filepath || join(homedir(), '.config', 'envision', 'config', `${getHostName()}-login.json`);
|
|
||||||
fileExists(this.filepath, { isFile: true });
|
|
||||||
}
|
|
||||||
async get(_key: string) {
|
|
||||||
try {
|
|
||||||
const filePath = this.filepath;
|
|
||||||
const data = readConfigFile(filePath);
|
|
||||||
return data;
|
|
||||||
} catch (error) {
|
|
||||||
console.log('get error', error);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async set(_key: string, value: any) {
|
|
||||||
try {
|
|
||||||
const data = readConfigFile(this.filepath);
|
|
||||||
const newData = { ...data, ...value };
|
|
||||||
writeConfigFile(this.filepath, newData);
|
|
||||||
} catch (error) {
|
|
||||||
console.log('set error', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async del() {
|
|
||||||
await unlink(this.filepath);
|
|
||||||
}
|
|
||||||
async loadCache(filePath: string) {
|
|
||||||
try {
|
|
||||||
const data = await readFile(filePath, 'utf-8');
|
|
||||||
const jsonData = JSON.parse(data);
|
|
||||||
return jsonData;
|
|
||||||
} catch (error) {
|
|
||||||
// console.log('loadCache error', error);
|
|
||||||
console.log('create new cache file:', filePath);
|
|
||||||
const defaultData = { loginUsers: [] };
|
|
||||||
writeConfigFile(filePath, defaultData);
|
|
||||||
return defaultData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async init() {
|
|
||||||
return await this.loadCache(this.filepath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { QueryLogin, QueryLoginOpts } from './query-login.ts';
|
|
||||||
import { MyCache } from '@kevisual/cache';
|
|
||||||
type QueryLoginNodeOptsWithoutCache = Omit<QueryLoginOpts, 'cache'>;
|
|
||||||
|
|
||||||
export class QueryLoginBrowser extends QueryLogin {
|
|
||||||
constructor(opts: QueryLoginNodeOptsWithoutCache) {
|
|
||||||
super({
|
|
||||||
...opts,
|
|
||||||
cache: new MyCache('login'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { QueryLogin, QueryLoginOpts } from './query-login.ts';
|
|
||||||
import { LoginNodeCache, StorageNode } from './login-node-cache.ts';
|
|
||||||
type QueryLoginNodeOptsWithoutCache = Omit<QueryLoginOpts, 'cache'>;
|
|
||||||
export const storage = new StorageNode();
|
|
||||||
await storage.loadCache();
|
|
||||||
export class QueryLoginNode extends QueryLogin {
|
|
||||||
constructor(opts: QueryLoginNodeOptsWithoutCache) {
|
|
||||||
super({
|
|
||||||
...opts,
|
|
||||||
storage,
|
|
||||||
cache: new LoginNodeCache(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,434 +0,0 @@
|
|||||||
import { Query, BaseQuery } from '@kevisual/query';
|
|
||||||
import type { Result, DataOpts } from '@kevisual/query/query';
|
|
||||||
import { setBaseResponse } from '@kevisual/query/query';
|
|
||||||
import { LoginCacheStore, CacheStore } from './login-cache.ts';
|
|
||||||
import { Cache } from './login-cache.ts';
|
|
||||||
|
|
||||||
export type QueryLoginOpts = {
|
|
||||||
query?: Query;
|
|
||||||
isBrowser?: boolean;
|
|
||||||
onLoad?: () => void;
|
|
||||||
storage?: Storage;
|
|
||||||
cache: Cache;
|
|
||||||
};
|
|
||||||
export type QueryLoginData = {
|
|
||||||
username?: string;
|
|
||||||
password: string;
|
|
||||||
email?: string;
|
|
||||||
};
|
|
||||||
export type QueryLoginResult = {
|
|
||||||
accessToken: string;
|
|
||||||
refreshToken: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class QueryLogin extends BaseQuery {
|
|
||||||
/**
|
|
||||||
* query login cache, 非实际操作, 一个cache的包裹模块
|
|
||||||
*/
|
|
||||||
cacheStore: CacheStore;
|
|
||||||
isBrowser: boolean;
|
|
||||||
load?: boolean;
|
|
||||||
storage: Storage;
|
|
||||||
onLoad?: () => void;
|
|
||||||
|
|
||||||
constructor(opts?: QueryLoginOpts) {
|
|
||||||
super({
|
|
||||||
query: opts?.query || new Query(),
|
|
||||||
});
|
|
||||||
this.cacheStore = new LoginCacheStore({ name: 'login', cache: opts?.cache! });
|
|
||||||
this.isBrowser = opts?.isBrowser ?? true;
|
|
||||||
this.init();
|
|
||||||
this.onLoad = opts?.onLoad;
|
|
||||||
this.storage = opts?.storage || localStorage;
|
|
||||||
}
|
|
||||||
setQuery(query: Query) {
|
|
||||||
this.query = query;
|
|
||||||
}
|
|
||||||
private async init() {
|
|
||||||
await this.cacheStore.init();
|
|
||||||
this.load = true;
|
|
||||||
this.onLoad?.();
|
|
||||||
}
|
|
||||||
async post<T = any>(data: any, opts?: DataOpts) {
|
|
||||||
try {
|
|
||||||
return this.query.post<T>({ path: 'user', ...data }, opts);
|
|
||||||
} catch (error) {
|
|
||||||
console.log('error', error);
|
|
||||||
return {
|
|
||||||
code: 400,
|
|
||||||
} as any;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 登录,
|
|
||||||
* @param data
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async login(data: QueryLoginData) {
|
|
||||||
const res = await this.post<QueryLoginResult>({ key: 'login', ...data });
|
|
||||||
if (res.code === 200) {
|
|
||||||
const { accessToken, refreshToken } = res?.data || {};
|
|
||||||
this.storage.setItem('token', accessToken || '');
|
|
||||||
await this.beforeSetLoginUser({ accessToken, refreshToken });
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 手机号登录
|
|
||||||
* @param data
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async loginByCode(data: { phone: string; code: string }) {
|
|
||||||
const res = await this.post<QueryLoginResult>({ path: 'sms', key: 'login', data });
|
|
||||||
if (res.code === 200) {
|
|
||||||
const { accessToken, refreshToken } = res?.data || {};
|
|
||||||
this.storage.setItem('token', accessToken || '');
|
|
||||||
await this.beforeSetLoginUser({ accessToken, refreshToken });
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 设置token
|
|
||||||
* @param token
|
|
||||||
*/
|
|
||||||
async setLoginToken(token: { accessToken: string; refreshToken: string }) {
|
|
||||||
const { accessToken, refreshToken } = token;
|
|
||||||
this.storage.setItem('token', accessToken || '');
|
|
||||||
await this.beforeSetLoginUser({ accessToken, refreshToken });
|
|
||||||
}
|
|
||||||
async loginByWechat(data: { code: string }) {
|
|
||||||
const res = await this.post<QueryLoginResult>({ path: 'wx', key: 'open-login', code: data.code });
|
|
||||||
if (res.code === 200) {
|
|
||||||
const { accessToken, refreshToken } = res?.data || {};
|
|
||||||
this.storage.setItem('token', accessToken || '');
|
|
||||||
await this.beforeSetLoginUser({ accessToken, refreshToken });
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 检测微信登录,登陆成功后,调用onSuccess,否则调用onError
|
|
||||||
* @param param0
|
|
||||||
*/
|
|
||||||
async checkWechat({ onSuccess, onError }: { onSuccess?: (res: QueryLoginResult) => void; onError?: (res: any) => void }) {
|
|
||||||
const url = new URL(window.location.href);
|
|
||||||
const code = url.searchParams.get('code');
|
|
||||||
const state = url.searchParams.get('state');
|
|
||||||
if (code && state) {
|
|
||||||
const res = await this.loginByWechat({ code });
|
|
||||||
if (res.code === 200) {
|
|
||||||
onSuccess?.(res.data);
|
|
||||||
} else {
|
|
||||||
onError?.(res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 登陆成功,需要获取用户信息进行缓存
|
|
||||||
* @param param0
|
|
||||||
*/
|
|
||||||
async beforeSetLoginUser({ accessToken, refreshToken, check401 }: { accessToken?: string; refreshToken?: string; check401?: boolean }) {
|
|
||||||
if (accessToken && refreshToken) {
|
|
||||||
const resUser = await this.getMe(accessToken, check401);
|
|
||||||
if (resUser.code === 200) {
|
|
||||||
const user = resUser.data;
|
|
||||||
if (user) {
|
|
||||||
this.cacheStore.setLoginUser({
|
|
||||||
user,
|
|
||||||
id: user.id,
|
|
||||||
accessToken,
|
|
||||||
refreshToken,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.error('登录失败');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 刷新token
|
|
||||||
* @param refreshToken
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async queryRefreshToken(refreshToken?: string) {
|
|
||||||
const _refreshToken = refreshToken || this.cacheStore.getRefreshToken();
|
|
||||||
let data = { refreshToken: _refreshToken };
|
|
||||||
if (!_refreshToken) {
|
|
||||||
await this.cacheStore.clearCurrentUser();
|
|
||||||
return {
|
|
||||||
code: 401,
|
|
||||||
message: '请先登录',
|
|
||||||
data: {} as any,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return this.post(
|
|
||||||
{ key: 'refreshToken', data },
|
|
||||||
{
|
|
||||||
afterResponse: async (response, ctx) => {
|
|
||||||
setBaseResponse(response);
|
|
||||||
return response as any;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 检查401错误,并刷新token, 如果refreshToken存在,则刷新token, 否则返回401
|
|
||||||
* 拦截请求,请使用run401Action, 不要直接使用 afterCheck401ToRefreshToken
|
|
||||||
* @param response
|
|
||||||
* @param ctx
|
|
||||||
* @param refetch
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async afterCheck401ToRefreshToken(response: Result, ctx?: { req?: any; res?: any; fetch?: any }, refetch?: boolean) {
|
|
||||||
const that = this;
|
|
||||||
if (response?.code === 401) {
|
|
||||||
const hasRefreshToken = await that.cacheStore.getRefreshToken();
|
|
||||||
if (hasRefreshToken) {
|
|
||||||
const res = await that.queryRefreshToken(hasRefreshToken);
|
|
||||||
if (res.code === 200) {
|
|
||||||
const { accessToken, refreshToken } = res?.data || {};
|
|
||||||
that.storage.setItem('token', accessToken || '');
|
|
||||||
await that.beforeSetLoginUser({ accessToken, refreshToken, check401: false });
|
|
||||||
if (refetch && ctx && ctx.req && ctx.req.url && ctx.fetch) {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
||||||
const url = ctx.req?.url;
|
|
||||||
const body = ctx.req?.body;
|
|
||||||
const headers = ctx.req?.headers;
|
|
||||||
const res = await ctx.fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
body: body,
|
|
||||||
headers: { ...headers, Authorization: `Bearer ${accessToken}` },
|
|
||||||
});
|
|
||||||
setBaseResponse(res);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
that.storage.removeItem('token');
|
|
||||||
await that.cacheStore.clearCurrentUser();
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return response as any;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 一个简单的401处理, 如果401,则刷新token, 如果refreshToken不存在,则返回401
|
|
||||||
* refetch 是否重新请求, 会有bug,无限循环,按需要使用
|
|
||||||
* TODO:
|
|
||||||
* @param response
|
|
||||||
* @param ctx
|
|
||||||
* @param opts
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async run401Action(
|
|
||||||
response: Result,
|
|
||||||
ctx?: { req?: any; res?: any; fetch?: any },
|
|
||||||
opts?: {
|
|
||||||
/**
|
|
||||||
* 是否重新请求, 会有bug,无限循环,按需要使用
|
|
||||||
*/
|
|
||||||
refetch?: boolean;
|
|
||||||
/**
|
|
||||||
* check之后的回调
|
|
||||||
*/
|
|
||||||
afterCheck?: (res: Result) => any;
|
|
||||||
/**
|
|
||||||
* 401处理后, 还是401, 则回调
|
|
||||||
*/
|
|
||||||
afterAlso401?: (res: Result) => any;
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
const that = this;
|
|
||||||
const refetch = opts?.refetch ?? false;
|
|
||||||
if (response?.code === 401) {
|
|
||||||
if (that.query.stop === true) {
|
|
||||||
return { code: 500, success: false, message: 'refresh token loading...' };
|
|
||||||
}
|
|
||||||
that.query.stop = true;
|
|
||||||
const res = await that.afterCheck401ToRefreshToken(response, ctx, refetch);
|
|
||||||
that.query.stop = false;
|
|
||||||
opts?.afterCheck?.(res);
|
|
||||||
if (res.code === 401) {
|
|
||||||
opts?.afterAlso401?.(res);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
} else {
|
|
||||||
return response as any;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 获取用户信息
|
|
||||||
* @param token
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async getMe(token?: string, check401: boolean = true) {
|
|
||||||
const _token = token || this.storage.getItem('token');
|
|
||||||
const that = this;
|
|
||||||
return that.post(
|
|
||||||
{ key: 'me' },
|
|
||||||
{
|
|
||||||
beforeRequest: async (config) => {
|
|
||||||
if (config.headers) {
|
|
||||||
config.headers['Authorization'] = `Bearer ${_token}`;
|
|
||||||
}
|
|
||||||
if (!_token) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
afterResponse: async (response, ctx) => {
|
|
||||||
if (response?.code === 401 && check401 && !token) {
|
|
||||||
return await that.afterCheck401ToRefreshToken(response, ctx);
|
|
||||||
}
|
|
||||||
return response as any;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 检查本地用户,如果本地用户存在,则返回本地用户,否则返回null
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async checkLocalUser() {
|
|
||||||
const user = await this.cacheStore.getCurrentUser();
|
|
||||||
if (user) {
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 检查本地token是否存在,简单的判断是否已经属于登陆状态
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async checkLocalToken() {
|
|
||||||
const token = this.storage.getItem('token');
|
|
||||||
return !!token;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 检查本地用户列表
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async getToken() {
|
|
||||||
const token = this.storage.getItem('token');
|
|
||||||
return token || '';
|
|
||||||
}
|
|
||||||
async beforeRequest(opts: any = {}) {
|
|
||||||
const token = this.storage.getItem('token');
|
|
||||||
if (token) {
|
|
||||||
opts.headers = { ...opts.headers, Authorization: `Bearer ${token}` };
|
|
||||||
}
|
|
||||||
return opts;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 请求更新,切换用户, 使用switchUser
|
|
||||||
* @param username
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
private async postSwitchUser(username: string) {
|
|
||||||
return this.post({ key: 'switchCheck', data: { username } });
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 切换用户
|
|
||||||
* @param username
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async switchUser(username: string) {
|
|
||||||
const localUserList = await this.cacheStore.getCurrentUserList();
|
|
||||||
const user = localUserList.find((userItem) => userItem.user!.username === username);
|
|
||||||
if (user) {
|
|
||||||
this.storage.setItem('token', user.accessToken || '');
|
|
||||||
await this.beforeSetLoginUser({ accessToken: user.accessToken, refreshToken: user.refreshToken });
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
data: {
|
|
||||||
accessToken: user.accessToken,
|
|
||||||
refreshToken: user.refreshToken,
|
|
||||||
},
|
|
||||||
success: true,
|
|
||||||
message: '切换用户成功',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const res = await this.postSwitchUser(username);
|
|
||||||
|
|
||||||
if (res.code === 200) {
|
|
||||||
const { accessToken, refreshToken } = res?.data || {};
|
|
||||||
this.storage.setItem('token', accessToken || '');
|
|
||||||
await this.beforeSetLoginUser({ accessToken, refreshToken });
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 退出登陆,去掉token, 并删除缓存
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async logout() {
|
|
||||||
this.storage.removeItem('token');
|
|
||||||
const users = await this.cacheStore.getCurrentUserList();
|
|
||||||
const tokens = users
|
|
||||||
.map((user) => {
|
|
||||||
return user?.accessToken;
|
|
||||||
})
|
|
||||||
.filter(Boolean);
|
|
||||||
this.cacheStore.delValue();
|
|
||||||
return this.post<Result>({ key: 'logout', data: { tokens } });
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 检查用户名的组,这个用户是否存在
|
|
||||||
* @param username
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async hasUser(username: string) {
|
|
||||||
const that = this;
|
|
||||||
return this.post<Result>(
|
|
||||||
{
|
|
||||||
path: 'org',
|
|
||||||
key: 'hasUser',
|
|
||||||
data: {
|
|
||||||
username,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
afterResponse: async (response, ctx) => {
|
|
||||||
if (response?.code === 401) {
|
|
||||||
const res = await that.afterCheck401ToRefreshToken(response, ctx, true);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
return response as any;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 检查登录状态
|
|
||||||
* @param token
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async checkLoginStatus(token: string) {
|
|
||||||
const res = await this.post({
|
|
||||||
path: 'user',
|
|
||||||
key: 'checkLoginStatus',
|
|
||||||
loginToken: token,
|
|
||||||
});
|
|
||||||
if (res.code === 200) {
|
|
||||||
const accessToken = res.data?.accessToken;
|
|
||||||
this.storage.setItem('token', accessToken || '');
|
|
||||||
await this.beforeSetLoginUser({ accessToken, refreshToken: res.data?.refreshToken });
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 使用web登录,创建url地址, 需要MD5和jsonwebtoken
|
|
||||||
*/
|
|
||||||
loginWithWeb(baseURL: string, { MD5, jsonwebtoken }: { MD5: any; jsonwebtoken: any }) {
|
|
||||||
const randomId = Math.random().toString(36).substring(2, 15);
|
|
||||||
const timestamp = Date.now();
|
|
||||||
const tokenSecret = 'xiao' + randomId;
|
|
||||||
const sign = MD5(`${tokenSecret}${timestamp}`).toString();
|
|
||||||
const token = jsonwebtoken.sign({ randomId, timestamp, sign }, tokenSecret, {
|
|
||||||
// 10分钟过期
|
|
||||||
expiresIn: 60 * 10, // 10分钟
|
|
||||||
});
|
|
||||||
const url = `${baseURL}/api/router?path=user&key=webLogin&p&loginToken=${token}&sign=${sign}&randomId=${randomId}`;
|
|
||||||
return { url, token, tokenSecret };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -30,20 +30,14 @@ export const runServer = async (port: number = 51515, listenPath = '127.0.0.1')
|
|||||||
}
|
}
|
||||||
_port = isPortAvailable;
|
_port = isPortAvailable;
|
||||||
}
|
}
|
||||||
const hasSocket = listenPath.includes('.sock');
|
|
||||||
if (hasSocket) {
|
app.listen(_port, listenPath, () => {
|
||||||
app.listen(listenPath, () => {
|
let showListenPath = listenPath;
|
||||||
console.log(`Server is running on ${listenPath}`);
|
if (listenPath === '::') {
|
||||||
});
|
showListenPath = 'localhost';
|
||||||
} else {
|
}
|
||||||
app.listen(_port, listenPath, () => {
|
console.log(`Server is running on ${'http'}://${showListenPath}:${_port}`);
|
||||||
let showListenPath = listenPath;
|
});
|
||||||
if (listenPath === '::') {
|
|
||||||
showListenPath = 'localhost';
|
|
||||||
}
|
|
||||||
console.log(`Server is running on ${'http'}://${showListenPath}:${_port}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
app.server.on([{
|
app.server.on([{
|
||||||
id: 'handle-all',
|
id: 'handle-all',
|
||||||
func: proxyRoute as any,
|
func: proxyRoute as any,
|
||||||
|
|||||||
38
assistant/src/services/init/common.ts
Normal file
38
assistant/src/services/init/common.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
export const configJson = `{
|
||||||
|
"name": "assistant-app",
|
||||||
|
"version": "1.0.1",
|
||||||
|
"description": "assistant-app package pnpm, node pkgs projects",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"start": "pm2 start apps/code-center/dist/app.mjs --name code-center",
|
||||||
|
"proxy": "pm2 start apps/page-proxy/dist/app.mjs --name page-proxy",
|
||||||
|
"preview": "pnpm i && ASSISTANT_CONFIG_DIR=/workspace/kevisual asst server -s"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-s3": "^3.990.0",
|
||||||
|
"@kevisual/oss": "^0.0.19",
|
||||||
|
"@kevisual/query": "^0.0.40",
|
||||||
|
"eventemitter3": "^5.0.4",
|
||||||
|
"@kevisual/router": "^0.0.70",
|
||||||
|
"@kevisual/use-config": "^1.0.30",
|
||||||
|
"ioredis": "^5.9.3",
|
||||||
|
"pg": "^8.18.0",
|
||||||
|
"pm2": "^6.0.14",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
|
"unstorage": "^1.17.4",
|
||||||
|
"dayjs": "^1.11.19",
|
||||||
|
"es-toolkit": "^1.44.0",
|
||||||
|
"node-cron": "^4.2.1",
|
||||||
|
"dotenv": "^17.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@kevisual/types": "^0.0.12",
|
||||||
|
"@types/bun": "^1.3.9",
|
||||||
|
"@types/crypto-js": "^4.2.2",
|
||||||
|
"@types/node": "^25.2.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
@@ -3,7 +3,7 @@ import path from 'node:path';
|
|||||||
import { checkFileExists, AssistantConfig, AssistantConfigData, parseHomeArg, parseHelpArg } from '@/module/assistant/index.ts';
|
import { checkFileExists, AssistantConfig, AssistantConfigData, parseHomeArg, parseHelpArg } from '@/module/assistant/index.ts';
|
||||||
import { chalk } from '@/module/chalk.ts';
|
import { chalk } from '@/module/chalk.ts';
|
||||||
import { Query } from '@kevisual/query/query';
|
import { Query } from '@kevisual/query/query';
|
||||||
import { installDeps } from '@/module/npm-install.ts'
|
import { checkNpmConfg } from './update-pkgs.ts';
|
||||||
export { parseHomeArg, parseHelpArg };
|
export { parseHomeArg, parseHelpArg };
|
||||||
export type AssistantInitOptions = {
|
export type AssistantInitOptions = {
|
||||||
path?: string;
|
path?: string;
|
||||||
@@ -118,56 +118,7 @@ export class AssistantInit extends AssistantConfig {
|
|||||||
console.log(chalk.green('助手 pnpm-workspace.yaml 文件创建成功'));
|
console.log(chalk.green('助手 pnpm-workspace.yaml 文件创建成功'));
|
||||||
}
|
}
|
||||||
const packagePath = path.join(this.configDir, 'assistant-app', 'package.json');
|
const packagePath = path.join(this.configDir, 'assistant-app', 'package.json');
|
||||||
if (!checkFileExists(packagePath, true)) {
|
checkNpmConfg(packagePath);
|
||||||
create = true;
|
|
||||||
fs.writeFileSync(
|
|
||||||
packagePath,
|
|
||||||
`{
|
|
||||||
"name": "assistant-app",
|
|
||||||
"version": "1.0.1",
|
|
||||||
"description": "assistant-app package pnpm, node pkgs projects",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"start": "pm2 start apps/code-center/dist/app.mjs --name code-center",
|
|
||||||
"proxy": "pm2 start apps/page-proxy/dist/app.mjs --name page-proxy",
|
|
||||||
"preview": "pnpm i && ASSISTANT_CONFIG_DIR=/workspace/kevisual asst server -s"
|
|
||||||
},
|
|
||||||
"keywords": [],
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"@aws-sdk/client-s3": "^3.990.0",
|
|
||||||
"@kevisual/oss": "^0.0.19",
|
|
||||||
"@kevisual/query": "^0.0.40",
|
|
||||||
"eventemitter3": "^5.0.4",
|
|
||||||
"@kevisual/router": "^0.0.70",
|
|
||||||
"@kevisual/use-config": "^1.0.30",
|
|
||||||
"ioredis": "^5.9.3",
|
|
||||||
"pg": "^8.18.0",
|
|
||||||
"pm2": "^6.0.14",
|
|
||||||
"crypto-js": "^4.2.0",
|
|
||||||
"unstorage": "^1.17.4",
|
|
||||||
"dayjs": "^1.11.19",
|
|
||||||
"es-toolkit": "^1.44.0",
|
|
||||||
"node-cron": "^4.2.1",
|
|
||||||
"dotenv": "^17.3.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@kevisual/types": "^0.0.12",
|
|
||||||
"@types/bun": "^1.3.9",
|
|
||||||
"@types/crypto-js": "^4.2.2",
|
|
||||||
"@types/node": "^25.2.3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
);
|
|
||||||
console.log(chalk.green('助手 package.json 文件创建成功, 正在安装依赖...'));
|
|
||||||
installDeps({ appPath: path.dirname(packagePath), isProduction: true }).then(() => {
|
|
||||||
console.log('------------------------------------------------');
|
|
||||||
console.log(chalk.green('助手依赖安装完成'));
|
|
||||||
console.log('------------------------------------------------');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
create,
|
create,
|
||||||
};
|
};
|
||||||
@@ -205,21 +156,4 @@ export class AssistantInit extends AssistantConfig {
|
|||||||
console.log(chalk.green('.gitignore 文件更新成功'));
|
console.log(chalk.green('.gitignore 文件更新成功'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected getDefaultInitAssistantConfig() {
|
|
||||||
const id = randomId();
|
|
||||||
return {
|
|
||||||
app: {
|
|
||||||
url: 'https://kevisual.cn',
|
|
||||||
id,
|
|
||||||
},
|
|
||||||
description: '助手配置文件',
|
|
||||||
docs: "https://kevisual.cn/root/cli/docs/",
|
|
||||||
home: '/root/home',
|
|
||||||
proxy: [],
|
|
||||||
share: {
|
|
||||||
enabled: false,
|
|
||||||
url: 'https://kevisual.cn/ws/proxy',
|
|
||||||
},
|
|
||||||
} as AssistantConfigData;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "assistant-app",
|
"name": "assistant-app",
|
||||||
"version": "1.0.1",
|
"version": "1.0.3",
|
||||||
"description": "assistant-app package pnpm, node pkgs projects",
|
"description": "assistant-app package pnpm, node pkgs projects",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -12,26 +12,25 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.990.0",
|
"@aws-sdk/client-s3": "^3.994.0",
|
||||||
"@kevisual/oss": "^0.0.19",
|
"@kevisual/oss": "^0.0.19",
|
||||||
"@kevisual/query": "^0.0.40",
|
"@kevisual/query": "^0.0.49",
|
||||||
"eventemitter3": "^5.0.4",
|
"@kevisual/router": "^0.0.83",
|
||||||
"@kevisual/router": "^0.0.70",
|
|
||||||
"@kevisual/use-config": "^1.0.30",
|
"@kevisual/use-config": "^1.0.30",
|
||||||
|
"dayjs": "^1.11.19",
|
||||||
|
"dotenv": "^17.3.1",
|
||||||
|
"es-toolkit": "^1.44.0",
|
||||||
|
"eventemitter3": "^5.0.4",
|
||||||
"ioredis": "^5.9.3",
|
"ioredis": "^5.9.3",
|
||||||
|
"node-cron": "^4.2.1",
|
||||||
"pg": "^8.18.0",
|
"pg": "^8.18.0",
|
||||||
"pm2": "^6.0.14",
|
"pm2": "^6.0.14",
|
||||||
"crypto-js": "^4.2.0",
|
"unstorage": "^1.17.4"
|
||||||
"unstorage": "^1.17.4",
|
|
||||||
"dayjs": "^1.11.19",
|
|
||||||
"es-toolkit": "^1.44.0",
|
|
||||||
"node-cron": "^4.2.1",
|
|
||||||
"dotenv": "^17.3.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@kevisual/types": "^0.0.12",
|
"@kevisual/types": "^0.0.12",
|
||||||
"@types/bun": "^1.3.9",
|
"@types/bun": "^1.3.9",
|
||||||
"@types/crypto-js": "^4.2.2",
|
"@types/node": "^25.3.0",
|
||||||
"@types/node": "^25.2.3"
|
"semver": "^7.7.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
111
assistant/src/services/init/update-pkgs.ts
Normal file
111
assistant/src/services/init/update-pkgs.ts
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import { configJson } from './common.ts';
|
||||||
|
import { checkFileExists } from '@/module/assistant/index.ts';
|
||||||
|
import { chalk } from '@/module/chalk.ts';
|
||||||
|
import { installDeps } from '@/module/npm-install.ts'
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { execSync } from 'node:child_process';
|
||||||
|
import semver from 'semver';
|
||||||
|
|
||||||
|
export const checkNpmConfg = (packagePath: string) => {
|
||||||
|
let create = false;
|
||||||
|
if (!checkFileExists(packagePath, true)) {
|
||||||
|
create = true;
|
||||||
|
fs.writeFileSync(packagePath, configJson);
|
||||||
|
console.log(chalk.green('助手 package.json 文件创建成功, 正在安装依赖...'));
|
||||||
|
installDeps({ appPath: path.dirname(packagePath), isProduction: true }).then(() => {
|
||||||
|
console.log('------------------------------------------------');
|
||||||
|
console.log(chalk.green('助手依赖安装完成'));
|
||||||
|
console.log('------------------------------------------------');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
checkNpmFileConfig(packagePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
create,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkNpmFileConfig = async (packagePath: string) => {
|
||||||
|
const existingConfig = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
|
||||||
|
const defaultConfig = JSON.parse(configJson);
|
||||||
|
const appPath = path.dirname(packagePath);
|
||||||
|
let needUpdate = false;
|
||||||
|
if (defaultConfig.version !== existingConfig.version) {
|
||||||
|
existingConfig.version = defaultConfig.version;
|
||||||
|
existingConfig.scripts = {
|
||||||
|
...existingConfig.scripts,
|
||||||
|
...defaultConfig.scripts,
|
||||||
|
};
|
||||||
|
needUpdate = true;
|
||||||
|
fs.writeFileSync(packagePath, JSON.stringify(existingConfig, null, 2));
|
||||||
|
console.log(chalk.yellow('助手 package.json 文件版本不匹配,正在更新...'));
|
||||||
|
}
|
||||||
|
// 收集需要更新的依赖列表
|
||||||
|
const depsToUpdate: string[] = [];
|
||||||
|
const devDepsToUpdate: string[] = [];
|
||||||
|
// 收集需要添加的依赖(不存在的)
|
||||||
|
const depsToAdd: string[] = [];
|
||||||
|
const devDepsToAdd: string[] = [];
|
||||||
|
|
||||||
|
// 检查 dependencies - 不存在或版本小于 defaultConfig
|
||||||
|
for (const dep in defaultConfig.dependencies) {
|
||||||
|
const defaultVersion = defaultConfig.dependencies[dep].replace(/^[~^]/, '');
|
||||||
|
const existingVersion = existingConfig.dependencies?.[dep]?.replace(/^[~^]/, '');
|
||||||
|
// console.log(chalk.blue(`检查 dependency: ${dep}, defaultVersion: ${defaultVersion}, existingVersion: ${existingVersion}`));
|
||||||
|
if (!existingVersion) {
|
||||||
|
// 依赖不存在,先添加到 package.json
|
||||||
|
existingConfig.dependencies = existingConfig.dependencies || {};
|
||||||
|
existingConfig.dependencies[dep] = defaultConfig.dependencies[dep];
|
||||||
|
depsToAdd.push(dep);
|
||||||
|
} else if (semver.lt(existingVersion, defaultVersion)) {
|
||||||
|
depsToUpdate.push(dep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 devDependencies - 不存在或版本小于 defaultConfig
|
||||||
|
for (const devDep in defaultConfig.devDependencies) {
|
||||||
|
const defaultVersion = defaultConfig.devDependencies[devDep].replace(/^[~^]/, '');
|
||||||
|
const existingVersion = existingConfig.devDependencies?.[devDep]?.replace(/^[~^]/, '');
|
||||||
|
// console.log(chalk.blue(`检查 devDependency: ${devDep}, defaultVersion: ${defaultVersion}, existingVersion: ${existingVersion}`));
|
||||||
|
if (!existingVersion) {
|
||||||
|
// 依赖不存在,先添加到 package.json
|
||||||
|
existingConfig.devDependencies = existingConfig.devDependencies || {};
|
||||||
|
existingConfig.devDependencies[devDep] = defaultConfig.devDependencies[devDep];
|
||||||
|
devDepsToAdd.push(devDep);
|
||||||
|
} else if (semver.lt(existingVersion, defaultVersion)) {
|
||||||
|
devDepsToUpdate.push(devDep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有新增的依赖,先写入 package.json
|
||||||
|
if (depsToAdd.length > 0 || devDepsToAdd.length > 0) {
|
||||||
|
fs.writeFileSync(packagePath, JSON.stringify(existingConfig, null, 2));
|
||||||
|
console.log(chalk.yellow(`新增的 dependencies: ${depsToAdd.join(', ') || '无'}`));
|
||||||
|
console.log(chalk.yellow(`新增的 devDependencies: ${devDepsToAdd.join(', ') || '无'}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (depsToUpdate.length > 0 || devDepsToUpdate.length > 0 || needUpdate) {
|
||||||
|
console.log(chalk.yellow(`需要更新的 dependencies: ${depsToUpdate.join(', ') || '无'}`));
|
||||||
|
console.log(chalk.yellow(`需要更新的 devDependencies: ${devDepsToUpdate.join(', ') || '无'}`));
|
||||||
|
console.log(chalk.green('正在执行 ncu -u 更新依赖版本...'));
|
||||||
|
|
||||||
|
// 执行 ncu -u 更新 package.json
|
||||||
|
try {
|
||||||
|
execSync('ncu -u', { cwd: appPath, stdio: 'inherit' });
|
||||||
|
console.log(chalk.green('ncu -u 执行完成'));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(chalk.red('ncu -u 执行失败:', error));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(chalk.green('正在安装依赖...'));
|
||||||
|
installDeps({ appPath, isProduction: true }).then(() => {
|
||||||
|
console.log('------------------------------------------------');
|
||||||
|
console.log(chalk.green('助手依赖安装完成'));
|
||||||
|
console.log('------------------------------------------------');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log(chalk.green('助手 package.json 文件已存在且依赖完整,无需更新'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
"@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.6",
|
"@opencode-ai/sdk": "^1.2.10",
|
||||||
"@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",
|
||||||
@@ -59,8 +59,8 @@
|
|||||||
"unstorage": "^1.17.4"
|
"unstorage": "^1.17.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@kevisual/api": "^0.0.52",
|
"@kevisual/api": "^0.0.55",
|
||||||
"@kevisual/cnb": "^0.0.26",
|
"@kevisual/cnb": "^0.0.28",
|
||||||
"@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",
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
"@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.2.3",
|
"@types/node": "^25.3.0",
|
||||||
"@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",
|
||||||
|
|||||||
751
pnpm-lock.yaml
generated
751
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
|||||||
import { Query } from '@kevisual/query/query';
|
import { Query } from '@kevisual/query/query';
|
||||||
import { getConfig, getEnvToken } from './get-config.ts';
|
import { getConfig, getEnvToken } from './get-config.ts';
|
||||||
import { QueryLoginNode, storage } from '@kevisual/api/login-node';
|
import { QueryLoginNode, StorageNode } from '@kevisual/api/login-node';
|
||||||
const config = getConfig();
|
const config = getConfig();
|
||||||
export const baseURL = config?.baseURL || 'https://kevisual.cn';
|
export const baseURL = config?.baseURL || 'https://kevisual.cn';
|
||||||
export { storage };
|
export const storage = new StorageNode({ load: true });
|
||||||
export const getBaseURL = () => {
|
export const getBaseURL = () => {
|
||||||
return baseURL;
|
return baseURL;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user