This commit is contained in:
2026-01-17 01:13:55 +08:00
parent 327f115ef2
commit cc043bfd7e
17 changed files with 768 additions and 790 deletions

1
.npmrc
View File

@@ -1,2 +1,3 @@
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN} //npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
//npm.cnb.cool/kevisual/registry/-/packages/:_authToken=${CNB_API_KEY}
//registry.npmjs.org/:_authToken=${NPM_TOKEN} //registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -6,7 +6,7 @@ import fs from 'node:fs';
// bun run src/index.ts -- // bun run src/index.ts --
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
const external = ['pm2', '@kevisual/hot-api', '@nut-tree-fork/nut-js']; const external = ['pm2', '@kevisual/hot-api', '@nut-tree-fork/nut-js', 'bun'];
/** /**
* *
* @param {string} p * @param {string} p

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.26.2", "packageManager": "pnpm@10.28.0",
"type": "module", "type": "module",
"files": [ "files": [
"dist", "dist",
@@ -41,18 +41,18 @@
} }
}, },
"devDependencies": { "devDependencies": {
"@kevisual/ai": "^0.0.19", "@kevisual/ai": "^0.0.20",
"@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.33", "@kevisual/query": "0.0.35",
"@kevisual/query-login": "0.0.7", "@kevisual/query-login": "0.0.7",
"@kevisual/router": "^0.0.52", "@kevisual/router": "^0.0.55",
"@kevisual/types": "^0.0.10", "@kevisual/types": "^0.0.11",
"@kevisual/use-config": "^1.0.21", "@kevisual/use-config": "^1.0.28",
"@types/bun": "^1.3.5", "@types/bun": "^1.3.6",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/node": "^25.0.3", "@types/node": "^25.0.9",
"@types/send": "^1.2.1", "@types/send": "^1.2.1",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"chalk": "^5.6.2", "chalk": "^5.6.2",
@@ -61,7 +61,7 @@
"dayjs": "^1.11.19", "dayjs": "^1.11.19",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"get-port": "^7.1.0", "get-port": "^7.1.0",
"inquirer": "^13.1.0", "inquirer": "^13.2.0",
"lodash-es": "^4.17.22", "lodash-es": "^4.17.22",
"nanoid": "^5.1.6", "nanoid": "^5.1.6",
"send": "^1.2.1", "send": "^1.2.1",
@@ -76,7 +76,7 @@
"access": "public" "access": "public"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.969.0", "@aws-sdk/client-s3": "^3.970.0",
"@kevisual/ha-api": "^0.0.6", "@kevisual/ha-api": "^0.0.6",
"@kevisual/oss": "^0.0.16", "@kevisual/oss": "^0.0.16",
"@kevisual/video-tools": "^0.0.13", "@kevisual/video-tools": "^0.0.13",
@@ -84,6 +84,6 @@
"lowdb": "^7.0.1", "lowdb": "^7.0.1",
"lru-cache": "^11.2.4", "lru-cache": "^11.2.4",
"pm2": "^6.0.14", "pm2": "^6.0.14",
"unstorage": "^1.17.3" "unstorage": "^1.17.4"
} }
} }

View File

@@ -1,6 +1,6 @@
import { App } from '@kevisual/router'; import { App } from '@kevisual/router';
import { SimpleRouter } from '@kevisual/router/simple'
// import { App } from '@kevisual/router/src/app.ts'; // import { App } from '@kevisual/router/src/app.ts';
import { HttpsPem } from '@/module/assistant/https/sign.ts';
// import { AssistantConfig } from '@/module/assistant/index.ts'; // import { AssistantConfig } from '@/module/assistant/index.ts';
import { AssistantInit, parseHomeArg } from '@/services/init/index.ts'; import { AssistantInit, parseHomeArg } from '@/services/init/index.ts';
import { configDir as HomeConfigDir } from '@/module/assistant/config/index.ts'; import { configDir as HomeConfigDir } from '@/module/assistant/config/index.ts';
@@ -21,8 +21,6 @@ export const assistantQuery = useContextKey('assistantQuery', () => {
return new AssistantQuery(assistantConfig); return new AssistantQuery(assistantConfig);
}); });
const httpsPem = new HttpsPem(assistantConfig);
export const runtime = useContextKey('runtime', () => { export const runtime = useContextKey('runtime', () => {
return { return {
type: 'client', type: 'client',
@@ -32,36 +30,22 @@ export const runtime = useContextKey('runtime', () => {
export const app: App = useContextKey<App>('app', () => { export const app: App = useContextKey<App>('app', () => {
const init = isInit; const init = isInit;
if (init) { if (init) {
const config = assistantConfig.getConfig(); return new App({
serverOptions: {
if (config?.https?.type !== 'https') { path: '/client/router',
console.log('http模式', 'http'); httpType: 'http',
return new App({ cors: {
serverOptions: { origin: '*',
path: '/client/router',
httpType: 'http',
cors: {
origin: '*',
},
io: true
}, },
}); io: true
}
}
return new App({
serverOptions: {
path: '/client/router',
httpType: 'https',
httpsCert: httpsPem.cert,
httpsKey: httpsPem.key,
cors: {
origin: '*',
}, },
io: true });
}, }
});
}); });
export const simpleRouter = useContextKey('simpleRouter', () => {
return new SimpleRouter();
});
app.route({ app.route({
path: 'router', path: 'router',

View File

@@ -155,15 +155,6 @@ export type AssistantConfigData = {
* share 是对外共享 pages 目录下的页面 * share 是对外共享 pages 目录下的页面
*/ */
auth?: AuthPermission; auth?: AuthPermission;
/**
* HTTPS 证书配置, 启用后,助手服务会启用 HTTPS 服务, 默认 HTTP
* 理论上也不需要https因为可以通过反向代理实现https
*/
https?: {
type?: 'https' | 'http';
keyPath?: string; // 证书私钥路径
certPath?: string; // 证书路径
};
}; };
let assistantConfig: AssistantConfigData; let assistantConfig: AssistantConfigData;
type AssistantConfigOptions = { type AssistantConfigOptions = {

View File

@@ -1,180 +0,0 @@
import { createCert } from '@kevisual/router/sign';
import path from 'node:path';
import fs from 'node:fs';
import { AssistantConfig } from '../config/index.ts';
import { checkFileExists } from '../file/index.ts';
import { chalk } from '@/module/chalk.ts';
import dayjs from 'dayjs';
type Attributes = {
name: string;
value: string;
};
type AltNames = {
type: number;
value?: string;
ip?: string;
};
export class HttpsPem {
assistantConfig: AssistantConfig;
key: string;
cert: string;
isHttps = false;
constructor(assistantConfig: AssistantConfig) {
this.assistantConfig = assistantConfig;
this.#initKeyCert();
}
#initKeyCert() {
this.assistantConfig.checkMounted();
const config = this.assistantConfig.getConfig();
if (config.https) {
const httpsType = config.https?.type || 'https';
if (httpsType !== 'https') {
// console.log(chalk.yellow('当前配置文件 https.type 不是 https, 不使用证书'));
return;
}
this.isHttps = true;
if (config.https.keyPath) {
const keyPath = config.https.keyPath;
const certPath = config.https.certPath;
if (checkFileExists(keyPath) && checkFileExists(certPath)) {
this.key = fs.readFileSync(keyPath, 'utf-8');
this.cert = fs.readFileSync(certPath, 'utf-8');
console.log(chalk.green('使用配置文件 https.keyPath 和 https.certPath 的证书'), keyPath, certPath);
return;
} else {
console.log(chalk.red('证书路径不存在,请检查配置文件 https.keyPath 和 https.certPath 是否正确'));
}
}
}
if(!this.isHttps) return;
const { key, cert } = this.getCert();
this.key = key;
this.cert = cert;
}
getPemDir() {
const configDir = this.assistantConfig.configPath?.configDir || process.cwd();
const pemDir = path.join(configDir, 'pem');
if (!checkFileExists(pemDir)) {
fs.mkdirSync(pemDir, { recursive: true });
}
return pemDir;
}
getCert() {
if (!this.assistantConfig.init) return;
const pemDir = this.getPemDir();
const pemPath = {
key: path.join(pemDir, 'https-private-key.pem'),
cert: path.join(pemDir, 'https-cert.pem'),
config: path.join(pemDir, 'https-config.json'),
};
const writeCreate = (opts: { key: string; cert: string; data: { createTime: number; expireTime: number } }) => {
fs.writeFileSync(pemPath.key, opts.key);
fs.writeFileSync(pemPath.cert, opts.cert);
fs.writeFileSync(pemPath.config, JSON.stringify(opts.data, null, 2));
};
if (!checkFileExists(pemPath.key) || !checkFileExists(pemPath.cert)) {
const { key, cert, data } = this.createCert();
writeCreate({ key, cert, data });
console.log(chalk.green('证书创建成功,浏览器需要导入当前证书'));
return {
key,
cert,
};
}
if (!checkFileExists(pemPath.config)) {
const data = this.createExpireData();
fs.writeFileSync(pemPath.config, JSON.stringify(data, null, 2));
}
const key = fs.readFileSync(pemPath.key, 'utf-8');
const cert = fs.readFileSync(pemPath.cert, 'utf-8');
const config = fs.readFileSync(pemPath.config, 'utf-8');
let expireTime = 0;
try {
const data = JSON.parse(config);
expireTime = data.expireTime;
if (typeof expireTime !== 'number') {
throw new Error('expireTime is not a number');
}
} catch (error) {
console.log(chalk.red('证书配置文件损坏,重新生成证书'));
}
const now = new Date().getTime();
if (now > expireTime) {
this.removeCert();
const { key, cert, data } = this.createCert();
writeCreate({ key, cert, data });
console.log(chalk.green('证书更新成功, 浏览器需要重新导入当前证书'));
return {
key,
cert,
};
}
return {
key,
cert,
};
}
createExpireData() {
const expireTime = new Date().getTime() + 365 * 24 * 60 * 60 * 1000;
const expireDate = dayjs(expireTime).format('YYYY-MM-DD HH:mm:ss');
return {
description: '手动导入证书到浏览器, https-cert.pem文件, 具体使用教程访问 https://kevisual.cn/root/pem-docs/',
createTime: new Date().getTime(),
expireDate,
expireTime,
};
}
/*
* 重新生成证书
*/
removeCert() {
const pemDir = this.getPemDir();
const pemPath = {
key: path.join(pemDir, 'https-private-key.pem'),
cert: path.join(pemDir, 'https-cert.pem'),
};
const oldPath = {
key: path.join(pemDir, 'https-private-key.pem.bak'),
cert: path.join(pemDir, 'https-cert.pem.bak'),
};
if (checkFileExists(pemPath.key)) {
fs.renameSync(pemPath.key, oldPath.key);
}
if (checkFileExists(pemPath.cert)) {
fs.renameSync(pemPath.cert, oldPath.cert);
}
}
/*
* 创建证书
* @param attrs 证书属性
* @param altNames 证书备用名称
*/
createCert(attrs?: Attributes[], altNames?: AltNames[]) {
const attributes = attrs || [];
const altNamesList = altNames || [];
const { key, cert } = createCert(
[
{
name: 'commonName',
value: 'localhost',
},
{
name: 'organizationName',
value: 'kevisual',
},
...attributes,
],
altNamesList,
);
const data = this.createExpireData();
return {
key,
cert,
data: {
...data,
},
};
}
}

View File

@@ -2,7 +2,6 @@ import * as http from 'http';
import * as fs from 'fs'; import * as fs from 'fs';
import { isBun } from './utils.ts'; import { isBun } from './utils.ts';
import { Readable } from 'stream'; import { Readable } from 'stream';
/** /**
* 文件流管道传输函数 * 文件流管道传输函数
* 将指定文件的内容通过流的方式传输给客户端响应 * 将指定文件的内容通过流的方式传输给客户端响应
@@ -112,4 +111,17 @@ export const pipeProxyReq = async (req: http.IncomingMessage, proxyReq: http.Cli
// Node.js标准环境下直接使用pipe进行流传输 // Node.js标准环境下直接使用pipe进行流传输
req.pipe(proxyReq, { end: true }); req.pipe(proxyReq, { end: true });
} }
}
export const pipeBusboy = async (req: http.IncomingMessage, res: http.ServerResponse, busboy: any) => {
if (isBun) {
// @ts-ignore
const bunRequest = req.bun.request;
const arrayBuffer = await bunRequest.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
busboy.end(buffer);
} else {
req.pipe(busboy);
}
} }

View File

@@ -0,0 +1,216 @@
// import Busboy from 'busboy';
// import { simpleRouter } from '../app.ts'
// import http from 'http';
// import path from 'path';
// import { createWriteStream } from 'fs';
// import { checkAuth } from '@/routes/index.ts';
// import { pipeBusboy } from '@/module/assistant/proxy/pipe.ts';
// simpleRouter.get('/client/upload', async (req, res) => {
// if (res.headersSent) return; // 如果响应已发送,不再处理
// res.writeHead(200, { 'Content-Type': 'application/json' });
// res.end(JSON.stringify({ message: 'Upload endpoint reached' }));
// })
// export const error = (msg: string, code = 500) => {
// return JSON.stringify({ code, message: msg });
// };
// export const parseIfJson = (data = '{}') => {
// try {
// const _data = JSON.parse(data);
// if (typeof _data === 'object') return _data;
// return {};
// } catch (error) {
// return {};
// }
// };
// const uploadResources = async (req: http.IncomingMessage, res: http.ServerResponse) => {
// const { tokenUser, token } = await checkAuth(req, res);
// if (!tokenUser) {
// res.end(error('Token is invalid.'));
// return;
// }
// const url = new URL(req.url || '', 'http://localhost');
// const share = !!url.searchParams.get('public');
// const meta = parseIfJson(url.searchParams.get('meta'));
// const noCheckAppFiles = !!url.searchParams.get('noCheckAppFiles');
// // 使用 busboy 解析 multipart/form-data
// const busboy = Busboy({ headers: req.headers, preservePath: true });
// const fields: any = {};
// const files: any[] = [];
// const filePromises: Promise<void>[] = [];
// let bytesReceived = 0;
// let bytesExpected = parseInt(req.headers['content-length'] || '0');
// busboy.on('field', (fieldname, value) => {
// fields[fieldname] = value;
// });
// busboy.on('file', (fieldname, fileStream, info) => {
// const { filename, encoding, mimeType } = info;
// const tempPath = path.join(cacheFilePath, `${Date.now()}-${Math.random().toString(36).substring(7)}`);
// const writeStream = createWriteStream(tempPath);
// const filePromise = new Promise<void>((resolve, reject) => {
// fileStream.on('data', (chunk) => {
// bytesReceived += chunk.length;
// if (bytesExpected > 0) {
// const progress = (bytesReceived / bytesExpected) * 100;
// const data = {
// progress: progress.toFixed(2),
// message: `Upload progress: ${progress.toFixed(2)}%`,
// };
// console.log('progress-upload', data);
// writeEvents(req, data);
// }
// });
// fileStream.pipe(writeStream);
// writeStream.on('finish', () => {
// files.push({
// filepath: tempPath,
// originalFilename: filename,
// mimetype: mimeType,
// });
// resolve();
// });
// writeStream.on('error', (err) => {
// reject(err);
// });
// });
// filePromises.push(filePromise);
// });
// busboy.on('finish', async () => {
// // 等待所有文件写入完成
// try {
// await Promise.all(filePromises);
// } catch (err) {
// logger.error(`File write error: ${err.message}`);
// res.end(error(`File write error: ${err.message}`));
// return;
// }
// const clearFiles = () => {
// files.forEach((file) => {
// if (file?.filepath && fs.existsSync(file.filepath)) {
// fs.unlinkSync(file.filepath);
// }
// });
// };
// // 检查是否有文件上传
// if (files.length === 0) {
// res.end(error('files is required'));
// return;
// }
// let { appKey, version, username, directory, description } = getKey(fields, ['appKey', 'version', 'username', 'directory', 'description']);
// let uid = tokenUser.id;
// if (username) {
// const user = await User.getUserByToken(token);
// const has = await user.hasUser(username, true);
// if (!has) {
// res.end(error('username is not found'));
// clearFiles();
// return;
// }
// const _user = await User.findOne({ where: { username } });
// uid = _user?.id || '';
// }
// if (!appKey || !version) {
// const config = await ConfigModel.getUploadConfig({ uid });
// if (config) {
// appKey = config.config?.data?.key || '';
// version = config.config?.data?.version || '';
// }
// }
// if (!appKey || !version) {
// res.end(error('appKey or version is not found, please check the upload config.'));
// clearFiles();
// return;
// }
// const { code, message } = validateDirectory(directory);
// if (code !== 200) {
// res.end(error(message));
// clearFiles();
// return;
// }
// // 逐个处理每个上传的文件
// const uploadedFiles = files;
// logger.info(
// 'upload files',
// uploadedFiles.map((item) => {
// return pick(item, ['filepath', 'originalFilename']);
// }),
// );
// const uploadResults = [];
// for (let i = 0; i < uploadedFiles.length; i++) {
// const file = uploadedFiles[i];
// // @ts-ignore
// const tempPath = file.filepath; // 文件上传时的临时路径
// const relativePath = file.originalFilename; // 保留表单中上传的文件名 (包含文件夹结构)
// // 比如 child2/b.txt
// const minioPath = `${username || tokenUser.username}/${appKey}/${version}${directory ? `/${directory}` : ''}/${relativePath}`;
// // 上传到 MinIO 并保留文件夹结构
// const isHTML = relativePath.endsWith('.html');
// const metadata: any = {};
// if (share) {
// metadata.share = 'public';
// }
// Object.assign(metadata, meta);
// await minioClient.fPutObject(bucketName, minioPath, tempPath, {
// 'Content-Type': getContentType(relativePath),
// 'app-source': 'user-app',
// 'Cache-Control': isHTML ? 'no-cache' : 'max-age=31536000, immutable', // 缓存一年
// ...metadata,
// });
// uploadResults.push({
// name: relativePath,
// path: minioPath,
// });
// fs.unlinkSync(tempPath); // 删除临时文件
// }
// if (!noCheckAppFiles) {
// const _data = { appKey, version, username, files: uploadResults, description, }
// if (_data.description) {
// delete _data.description;
// }
// // 受控
// const r = await app.call({
// path: 'app',
// key: 'uploadFiles',
// payload: {
// token: token,
// data: _data,
// },
// });
// const data: any = {
// code: r.code,
// data: {
// app: r.body,
// upload: uploadResults,
// },
// };
// if (r.message) {
// data.message = r.message;
// }
// console.log('upload data', data);
// res.writeHead(200, { 'Content-Type': 'application/json' });
// res.end(JSON.stringify(data));
// } else {
// res.writeHead(200, { 'Content-Type': 'application/json' });
// res.end(
// JSON.stringify({
// code: 200,
// data: {
// detect: [],
// upload: uploadResults,
// },
// }),
// );
// }
// });
// pipeBusboy(req, res, busboy);
// }

View File

@@ -34,7 +34,7 @@ export const getTokenUserCache = async (token: string) => {
} }
return res; return res;
} }
const checkAuth = async (ctx: any, isAdmin = false) => { export const checkAuth = async (ctx: any, isAdmin = false) => {
const config = assistantConfig.getConfig(); const config = assistantConfig.getConfig();
const { auth = {} } = config; const { auth = {} } = config;
const token = ctx.query.token; const token = ctx.query.token;

View File

@@ -35,12 +35,11 @@ export const runServer = async (port: number = 51515, listenPath = '127.0.0.1')
}); });
} else { } else {
app.listen(_port, listenPath, () => { app.listen(_port, listenPath, () => {
const protocol = assistantConfig.getHttps().protocol;
let showListenPath = listenPath; let showListenPath = listenPath;
if (listenPath === '::') { if (listenPath === '::') {
showListenPath = 'localhost'; showListenPath = 'localhost';
} }
console.log(`Server is running on ${protocol}://${showListenPath}:${_port}`); console.log(`Server is running on ${'http'}://${showListenPath}:${_port}`);
}); });
} }
app.server.on([{ app.server.on([{

View File

@@ -2,7 +2,6 @@ import fs from 'node:fs';
import path from 'node:path'; 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 { HttpsPem } from '@/module/assistant/https/sign.ts';
import { Query } from '@kevisual/query/query'; import { Query } from '@kevisual/query/query';
import { installDeps } from '@/module/npm-install.ts' import { installDeps } from '@/module/npm-install.ts'
export { parseHomeArg, parseHelpArg }; export { parseHomeArg, parseHelpArg };
@@ -77,14 +76,6 @@ export class AssistantInit extends AssistantConfig {
fs.writeFileSync(appsConfig, JSON.stringify({ description: 'apps manager.', list: [] })); fs.writeFileSync(appsConfig, JSON.stringify({ description: 'apps manager.', list: [] }));
console.log(chalk.green('助手应用配置文件 apps.json 创建成功')); console.log(chalk.green('助手应用配置文件 apps.json 创建成功'));
} }
// create pem dir //
const pemDir = path.join(this.configPath?.configDir, 'pem');
const httpsPem = new HttpsPem(this);
if (httpsPem.isHttps) {
if (!checkFileExists(pemDir)) {
console.log(chalk.green('助手证书目录创建成功'));
}
}
} }
createAssistantConfig() { createAssistantConfig() {
const assistantPath = this.configPath?.configPath; const assistantPath = this.configPath?.configPath;
@@ -218,11 +209,4 @@ export class AssistantInit extends AssistantConfig {
}, },
} as AssistantConfigData; } as AssistantConfigData;
} }
getHttps() {
const https = this.getConfig()?.https || {};
return {
https,
protocol: https?.type === 'https' ? 'https' : 'http',
};
}
} }

View File

@@ -3,6 +3,7 @@
// @ts-ignore // @ts-ignore
import pkg from './package.json'; import pkg from './package.json';
// bun run src/index.ts -- // bun run src/index.ts --
const external = ['bun'];
await Bun.build({ await Bun.build({
target: 'node', target: 'node',
format: 'esm', format: 'esm',
@@ -11,9 +12,8 @@ await Bun.build({
naming: { naming: {
entry: 'envision.js', entry: 'envision.js',
}, },
external: external,
define: { define: {
ENVISION_VERSION: JSON.stringify(pkg.version), ENVISION_VERSION: JSON.stringify(pkg.version),
}, },
env: 'ENVISION_*',
}); });

View File

@@ -1,6 +1,6 @@
{ {
"name": "@kevisual/cli", "name": "@kevisual/cli",
"version": "0.0.80", "version": "0.0.82",
"description": "envision 命令行工具", "description": "envision 命令行工具",
"type": "module", "type": "module",
"basename": "/root/cli", "basename": "/root/cli",
@@ -45,15 +45,17 @@
"@kevisual/app": "^0.0.2", "@kevisual/app": "^0.0.2",
"@kevisual/context": "^0.0.4", "@kevisual/context": "^0.0.4",
"@kevisual/hot-api": "^0.0.3", "@kevisual/hot-api": "^0.0.3",
"@kevisual/use-config": "^1.0.26", "@kevisual/use-config": "^1.0.28",
"@nut-tree-fork/nut-js": "^4.2.6", "@nut-tree-fork/nut-js": "^4.2.6",
"@types/busboy": "^1.5.4",
"busboy": "^1.6.0",
"eventemitter3": "^5.0.1", "eventemitter3": "^5.0.1",
"lowdb": "^7.0.1", "lowdb": "^7.0.1",
"lru-cache": "^11.2.4", "lru-cache": "^11.2.4",
"micromatch": "^4.0.8", "micromatch": "^4.0.8",
"pm2": "^6.0.14", "pm2": "^6.0.14",
"semver": "^7.7.3", "semver": "^7.7.3",
"unstorage": "^1.17.3" "unstorage": "^1.17.4"
}, },
"devDependencies": { "devDependencies": {
"@kevisual/dts": "^0.0.3", "@kevisual/dts": "^0.0.3",
@@ -65,7 +67,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.0.8", "@types/node": "^25.0.9",
"@types/semver": "^7.7.1", "@types/semver": "^7.7.1",
"chalk": "^5.6.2", "chalk": "^5.6.2",
"commander": "^14.0.2", "commander": "^14.0.2",

1002
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1 @@
# 可视化控制台工具 # 一个简单的cli工具
## 1. 上传文件
## 2. 下载文件
## 3. 同步模板

View File

@@ -14,7 +14,7 @@ const parseIfJson = (str: string) => {
return {}; return {};
} }
}; };
const command = new Command('npm').description('npm command show publish and set .npmrc').action(async (options) => {}); const command = new Command('npm').description('npm command show publish and set .npmrc').action(async (options) => { });
const publish = new Command('publish') const publish = new Command('publish')
.argument('[registry]') .argument('[registry]')
.option('-p --proxy', 'proxy') .option('-p --proxy', 'proxy')
@@ -33,6 +33,10 @@ const publish = new Command('publish')
name: 'npm', name: 'npm',
value: 'npm', value: 'npm',
}, },
{
name: 'cnb',
value: 'cnb'
}
], ],
}); });
} }
@@ -60,6 +64,9 @@ const publish = new Command('publish')
case 'npm': case 'npm':
cmd = 'npm publish --registry https://registry.npmjs.org'; cmd = 'npm publish --registry https://registry.npmjs.org';
break; break;
case 'cnb':
cmd = 'npm publish --registry https://npm.cnb.cool/kevisual/registry/-/packages/';
break;
default: default:
cmd = 'npm publish --registry https://npm.xiongxiao.me'; cmd = 'npm publish --registry https://npm.xiongxiao.me';
break; break;
@@ -136,6 +143,7 @@ const npmrc = new Command('set')
const npmrcContent = const npmrcContent =
config?.npmrc || config?.npmrc ||
`//npm.xiongxiao.me/:_authToken=\${ME_NPM_TOKEN} `//npm.xiongxiao.me/:_authToken=\${ME_NPM_TOKEN}
//npm.cnb.cool/kevisual/registry/-/packages/:_authToken=\${CNB_API_KEY}
//registry.npmjs.org/:_authToken=\${NPM_TOKEN} //registry.npmjs.org/:_authToken=\${NPM_TOKEN}
`; `;
const execPath = process.cwd(); const execPath = process.cwd();

View File

@@ -8,7 +8,7 @@ import { logger, printClickableLink } from '@/module/logger.ts';
import { chalk } from '@/module/chalk.ts'; import { chalk } from '@/module/chalk.ts';
import path from 'node:path'; import path from 'node:path';
import { fileIsExist } from '@/uitls/file.ts'; import { fileIsExist } from '@/uitls/file.ts';
import { confirm } from '@inquirer/prompts'
const command = new Command('sync') const command = new Command('sync')
.option('-d --dir <dir>') .option('-d --dir <dir>')
.description('同步项目') .description('同步项目')
@@ -33,7 +33,19 @@ const syncUpload = new Command('upload')
}; };
const filepath = sync.getRelativePath(opts.file); const filepath = sync.getRelativePath(opts.file);
const newInfos = []; const newInfos = [];
const uploadLength = syncList.length;
logger.info(`开始上传文件,总计 ${uploadLength} 个文件`);
if (uploadLength > 100) {
// 提示用户确认
const shouldContinue = await confirm({
message: `即将上传 ${uploadLength} 个文件,是否继续?`,
default: false,
});
if (!shouldContinue) {
logger.info('已取消上传');
return;
}
}
for (const item of syncList) { for (const item of syncList) {
if (!item.auth || !item.exist) { if (!item.auth || !item.exist) {
nodonwArr.push(item); nodonwArr.push(item);