更新功能实现

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 #!/usr/bin/env node
import color from "picocolors"; 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"; 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) { while (true) {
const selected = await showMainMenu(); const selected = await showMainMenu();
console.clear(); 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")); console.log(color.cyan("\n正在启动: " + selected.name + "...\n"));
try { try {

View File

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

View File

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

29
package-lock.json generated
View File

@ -1,19 +1,18 @@
{ {
"name": "@yinshuangxi/yinx-cli", "name": "@yinshuangxi/yinx-cli",
"version": "1.0.7", "version": "1.1.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@yinshuangxi/yinx-cli", "name": "@yinshuangxi/yinx-cli",
"version": "1.0.7", "version": "1.1.3",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@gltf-transform/core": "^3.10.1", "@gltf-transform/core": "^3.10.1",
"@gltf-transform/extensions": "^3.10.1", "@gltf-transform/extensions": "^3.10.1",
"@gltf-transform/functions": "^3.10.1", "@gltf-transform/functions": "^3.10.1",
"boxen": "^8.0.1", "boxen": "^8.0.1",
"figlet": "^1.9.4",
"picocolors": "^1.1.1" "picocolors": "^1.1.1"
}, },
"bin": { "bin": {
@ -741,15 +740,6 @@
"simple-swizzle": "^0.2.2" "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": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -1132,21 +1122,6 @@
], ],
"license": "BSD-3-Clause" "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": { "node_modules/for-each": {
"version": "0.3.5", "version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",

View File

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