更新功能实现
This commit is contained in:
25
index.js
25
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 {
|
||||
|
||||
42
lib/grid.js
42
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;
|
||||
|
||||
38
lib/menu.js
38
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 };
|
||||
|
||||
@ -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
29
package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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 |
Reference in New Issue
Block a user