diff --git a/index.js b/index.js index 1ac0e3c..30ba6e7 100644 --- a/index.js +++ b/index.js @@ -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 { diff --git a/lib/grid.js b/lib/grid.js index 0d99a73..7c4192b 100644 --- a/lib/grid.js +++ b/lib/grid.js @@ -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; diff --git a/lib/menu.js b/lib/menu.js index 2303513..4e357f5 100644 --- a/lib/menu.js +++ b/lib/menu.js @@ -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 }; diff --git a/lib/update.js b/lib/update.js index a6162e8..03de44f 100644 --- a/lib/update.js +++ b/lib/update.js @@ -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) { diff --git a/package-lock.json b/package-lock.json index 8c2032f..43095bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 3c31366..a9a7f7b 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/xw_20251220170040.png b/xw_20251220170040.png index fb4dc95..dcc2be9 100644 Binary files a/xw_20251220170040.png and b/xw_20251220170040.png differ diff --git a/xw_20251220170102.png b/xw_20251220170102.png deleted file mode 100644 index c01e31e..0000000 Binary files a/xw_20251220170102.png and /dev/null differ