更新功能实现

This commit is contained in:
yinsx
2025-12-22 09:43:46 +08:00
parent 29a7a1e626
commit dd99e932b4
8 changed files with 78 additions and 68 deletions

View File

@ -1,14 +1,35 @@
#!/usr/bin/env node
import color from "picocolors";
import { showMainMenu } from "./lib/menu.js";
import { execSync } from "child_process";
import { createRequire } from "module";
import { showMainMenu, setUpdateInfo } from "./lib/menu.js";
import { checkUpdate } from "./lib/update.js";
await checkUpdate();
const require = createRequire(import.meta.url);
const pkg = require("./package.json");
const updateResult = await checkUpdate();
if (updateResult) {
setUpdateInfo(updateResult);
}
while (true) {
const selected = await showMainMenu();
console.clear();
if (selected.isUpdate) {
console.log(color.cyan("\n正在更新...\n"));
try {
execSync(`npm i -g ${pkg.name}@latest`, { stdio: "inherit" });
console.log(color.green("\n✅ 更新完成!请重新运行 yinx\n"));
process.exit(0);
} catch {
console.log(color.red("\n❌ 更新失败,请手动运行: npm i -g " + pkg.name + "@latest\n"));
process.exit(1);
}
}
console.log(color.cyan("\n正在启动: " + selected.name + "...\n"));
try {

View File

@ -23,10 +23,15 @@ export async function gridSelect(options) {
cols = 3,
colWidth = 24,
title = "",
renderHeader = null
renderHeader = null,
updateInfo = null,
headerGap = 2, // header后的空行数
menuGap = 2, // 菜单和提示文字的间隔
rowGap = 1 // 每行菜单后的空行数
} = options;
let current = 0;
let onUpdate = updateInfo ? true : false; // 如果有更新,默认选中更新按钮
let resolved = false;
const rows = Math.ceil(items.length / cols);
const termWidth = process.stdout.columns || 80;
@ -37,19 +42,18 @@ export async function gridSelect(options) {
clearScreen();
if (renderHeader) {
console.log(renderHeader());
console.log(renderHeader(onUpdate));
}
console.log("");
for (let i = 0; i < headerGap; i++) console.log("");
if (title) {
const titlePad = " ".repeat(Math.max(0, Math.floor((termWidth - title.length - 4) / 2)));
console.log(titlePad + color.bgMagenta(color.white(` ${title} `)));
}
console.log("");
console.log(pad + color.dim("↑ ↓ ← → 选择 | Enter 确认 | Esc 退出"));
console.log("\n");
for (let i = 0; i < menuGap; i++) console.log("");
for (let row = 0; row < rows; row++) {
let line = "";
@ -59,7 +63,7 @@ export async function gridSelect(options) {
const idx = row * cols + col;
if (idx < items.length) {
const item = items[idx];
if (idx === current) {
if (!onUpdate && idx === current) {
line += color.cyan("[" + item.name + "]");
} else {
line += " " + item.name + " ";
@ -71,7 +75,7 @@ export async function gridSelect(options) {
console.log(pad + line);
console.log(pad + color.dim(descLine));
console.log("\n");
for (let i = 0; i < rowGap; i++) console.log("");
}
}
@ -87,21 +91,35 @@ export async function gridSelect(options) {
switch (key.name) {
case "up":
if (row > 0) { current -= cols; render(); }
if (onUpdate) {
// 已在更新按钮,不能再上
} else if (row === 0 && updateInfo) {
onUpdate = true; render();
} else if (row > 0) {
current -= cols; render();
}
break;
case "down":
if (row < rows - 1 && current + cols < items.length) { current += cols; render(); }
if (onUpdate) {
onUpdate = false; render();
} else if (row < rows - 1 && current + cols < items.length) {
current += cols; render();
}
break;
case "left":
if (col > 0) { current--; render(); }
if (!onUpdate && col > 0) { current--; render(); }
break;
case "right":
if (col < cols - 1 && current < items.length - 1) { current++; render(); }
if (!onUpdate && col < cols - 1 && current < items.length - 1) { current++; render(); }
break;
case "return":
resolved = true;
stopKeypress();
setImmediate(() => resolve(items[current]));
if (onUpdate) {
setImmediate(() => resolve({ isUpdate: true }));
} else {
setImmediate(() => resolve(items[current]));
}
break;
case "escape":
resolved = true;

View File

@ -1,6 +1,5 @@
import color from "picocolors";
import boxen from "boxen";
import figlet from "figlet";
import { gridSelect } from "./grid.js";
import * as convertTool from "./convert/index.js";
import * as ktx2Tool from "./ktx2/index.js";
@ -20,11 +19,7 @@ let poemConfig = {
borderColor: "cyan",
};
let titleConfig = {
text: "Zguiy Tool Box",
font: "Standard",
color: "magenta",
};
let updateInfo = null;
const tools = [
{ name: "格式转换", desc: "支持多种模型格式转换", tool: convertTool },
@ -47,10 +42,8 @@ export function setPoemStyle(style) {
Object.assign(poemConfig, style);
}
export function setTitle(text, font = "Standard", titleColor = "magenta") {
titleConfig.text = text;
titleConfig.font = font;
titleConfig.color = titleColor;
export function setUpdateInfo(info) {
updateInfo = info;
}
function renderPoem() {
@ -67,17 +60,21 @@ function renderPoem() {
});
}
function renderTitle() {
const art = figlet.textSync(titleConfig.text, { font: titleConfig.font });
const termWidth = process.stdout.columns || 80;
return art.split("\n").map(line => {
const pad = Math.max(0, Math.floor((termWidth - line.length) / 2));
return " ".repeat(pad) + color[titleConfig.color](line);
}).join("\n");
function renderUpdateInfo(selected) {
if (!updateInfo) return "";
const btn = selected ? color.cyan("[ 立即更新 ]") :color.white(" 立即更新 ") ;
const msg = `📦 发现新版本: ${color.red(updateInfo.current)}${color.green(updateInfo.latest)} ${btn}`;
return "\n" + boxen(color.yellow(msg), {
padding: { top: 0, bottom: 0, left: 2, right: 2 },
borderStyle: "round",
borderColor: "green",
textAlignment: "center",
float: "center",
});
}
function renderHeader() {
return renderPoem() + "\n\n" + renderTitle();
function renderHeader(onUpdate) {
return renderPoem() + renderUpdateInfo(onUpdate);
}
export async function showMainMenu() {
@ -86,7 +83,8 @@ export async function showMainMenu() {
cols: 3,
colWidth: 24,
renderHeader: renderHeader,
updateInfo: updateInfo,
});
}
export { tools, poemConfig, titleConfig };
export { tools, poemConfig };

View File

@ -1,4 +1,3 @@
import color from "picocolors";
import { createRequire } from "module";
const require = createRequire(import.meta.url);
@ -6,17 +5,17 @@ const pkg = require("../package.json");
export async function checkUpdate() {
try {
const res = await fetch(`https://registry.npmmirror.com/${pkg.name}/latest`, {
const res = await fetch(`https://registry.npmjs.org/${pkg.name}/latest`, {
signal: AbortSignal.timeout(3000)
});
if (!res.ok) return;
if (!res.ok) return null;
const data = await res.json();
const latest = data.version;
if (latest && latest !== pkg.version && isNewer(latest, pkg.version)) {
console.log(color.yellow(`\n📦 发现新版本: ${color.red(pkg.version)}${color.green(latest)}`));
console.log(color.cyan(` 运行 ${color.bold("yinx upgrade")} 进行更新\n`));
return { current: pkg.version, latest };
}
} catch {}
return null;
}
function isNewer(latest, current) {

29
package-lock.json generated
View File

@ -1,19 +1,18 @@
{
"name": "@yinshuangxi/yinx-cli",
"version": "1.0.7",
"version": "1.1.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@yinshuangxi/yinx-cli",
"version": "1.0.7",
"version": "1.1.3",
"license": "MIT",
"dependencies": {
"@gltf-transform/core": "^3.10.1",
"@gltf-transform/extensions": "^3.10.1",
"@gltf-transform/functions": "^3.10.1",
"boxen": "^8.0.1",
"figlet": "^1.9.4",
"picocolors": "^1.1.1"
},
"bin": {
@ -741,15 +740,6 @@
"simple-swizzle": "^0.2.2"
}
},
"node_modules/commander": {
"version": "14.0.2",
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz",
"integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==",
"license": "MIT",
"engines": {
"node": ">=20"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -1132,21 +1122,6 @@
],
"license": "BSD-3-Clause"
},
"node_modules/figlet": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/figlet/-/figlet-1.9.4.tgz",
"integrity": "sha512-uN6QE+TrzTAHC1IWTyrc4FfGo2KH/82J8Jl1tyKB7+z5DBit/m3D++Iu5lg91qJMnQQ3vpJrj5gxcK/pk4R9tQ==",
"license": "MIT",
"dependencies": {
"commander": "^14.0.0"
},
"bin": {
"figlet": "bin/index.js"
},
"engines": {
"node": ">= 17.0.0"
}
},
"node_modules/for-each": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",

View File

@ -1,6 +1,6 @@
{
"name": "@yinshuangxi/yinx-cli",
"version": "1.1.3",
"version": "1.1.5",
"description": "子归云工具箱-你想要的都在这",
"main": "dist/index.js",
"type": "module",
@ -37,7 +37,6 @@
"@gltf-transform/extensions": "^3.10.1",
"@gltf-transform/functions": "^3.10.1",
"boxen": "^8.0.1",
"figlet": "^1.9.4",
"picocolors": "^1.1.1"
},
"devDependencies": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB