第一版

This commit is contained in:
yinsx
2025-12-16 16:21:26 +08:00
parent 0c73ef2547
commit 8ba7e635f5
15 changed files with 1006 additions and 30 deletions

View File

@ -2,7 +2,13 @@
"permissions": { "permissions": {
"allow": [ "allow": [
"Bash(cat:*)", "Bash(cat:*)",
"Bash(npm install:*)" "Bash(npm install:*)",
"Bash(node test-poem.js:*)",
"Bash(printf:*)",
"Bash(timeout 2 node:*)",
"Bash(npm ls:*)",
"Bash(timeout 1 node:*)",
"Bash(node -e:*)"
] ]
} }
} }

52
cli.js
View File

@ -1,35 +1,35 @@
#!/usr/bin/env node #!/usr/bin/env node
import color from "picocolors"; import color from "picocolors";
import { checkToktx, scanImages, compressAll } from "./lib/compressor.js"; import { showMainMenu } from "./lib/menu.js";
import { runInteractive, showSummary } from "./lib/ui.js";
// 检查 toktx // 主循环
checkToktx(); while (true) {
const selected = await showMainMenu();
// 运行交互界面 console.clear();
const [exts, quality, encoding, mipmap, outputOpts] = await runInteractive(); console.log(color.cyan(`\n正在启动: ${selected.name}...\n`));
const config = { exts, quality, encoding, mipmap, outputOpts }; try {
const tool = await import(selected.module);
const result = await tool.run();
// 显示配置摘要 // 返回主菜单
showSummary(config); if (result === "back") continue;
// 扫描文件 // 工具完成后退出
const { images, cwd } = scanImages(exts); break;
} catch (err) {
console.log(color.yellow(`\n⚠️ ${selected.name} 模块尚未实现`));
console.log(color.dim(err.message));
console.log(color.dim("\n按任意键返回主菜单..."));
if (images.length === 0) { // 等待按键
console.log(color.yellow("当前目录没有匹配的图片")); await new Promise(resolve => {
process.exit(0); process.stdin.setRawMode(true);
} process.stdin.once("data", () => {
process.stdin.setRawMode(false);
console.log(`📁 找到 ${color.cyan(images.length)} 个待转换文件\n`); resolve();
});
// 执行压缩 });
const { total, failed } = await compressAll(images, config, cwd); }
// 显示结果
if (failed > 0) {
console.log(color.yellow(`\n⚠️ 完成,但有 ${failed} 个文件失败`));
} else {
console.log(color.green("\n🎉 全部文件压缩完成!"));
} }

46
lib/bigtext.js Normal file
View File

@ -0,0 +1,46 @@
import color from "picocolors";
// 计算字符串显示宽度中文2英文1
function strWidth(str) {
let width = 0;
for (const char of str) {
width += char.charCodeAt(0) > 127 ? 2 : 1;
}
return width;
}
// 大字效果框
export function bigText(text, options = {}) {
const {
style = "double",
textColor = "yellow",
padding = 2,
} = options;
const chars = {
block: { tl: "█", tr: "█", bl: "█", br: "█", h: "█", v: "█" },
double: { tl: "╔", tr: "╗", bl: "╚", br: "╝", h: "═", v: "║" },
simple: { tl: "┌", tr: "┐", bl: "└", br: "┘", h: "─", v: "│" },
};
const c = chars[style] || chars.double;
const lines = text.split("\n");
const maxWidth = Math.max(...lines.map(strWidth));
const boxWidth = maxWidth + padding * 2;
const result = [];
result.push(c.tl + c.h.repeat(boxWidth) + c.tr);
result.push(c.v + " ".repeat(boxWidth) + c.v);
for (const line of lines) {
const lineWidth = strWidth(line);
const rightPad = maxWidth - lineWidth;
result.push(c.v + " ".repeat(padding) + line + " ".repeat(rightPad + padding) + c.v);
}
result.push(c.v + " ".repeat(boxWidth) + c.v);
result.push(c.bl + c.h.repeat(boxWidth) + c.br);
const box = result.join("\n");
return color[textColor] ? color[textColor](box) : box;
}

119
lib/grid.js Normal file
View File

@ -0,0 +1,119 @@
import color from "picocolors";
import { initKeypress, onKey } from "./keyboard.js";
// 计算字符串显示宽度
function strWidth(str) {
let width = 0;
for (const char of str) {
width += char.charCodeAt(0) > 127 ? 2 : 1;
}
return width;
}
// 右填充到指定宽度
function padEnd(str, width) {
const diff = width - strWidth(str);
return str + " ".repeat(Math.max(0, diff));
}
/**
* 网格选择菜单
*/
export async function gridSelect(options) {
const {
items,
cols = 3,
colWidth = 24,
title = "",
renderHeader = null
} = options;
let current = 0;
const rows = Math.ceil(items.length / cols);
const termWidth = process.stdout.columns || 80;
const totalWidth = cols * colWidth;
const pad = " ".repeat(Math.max(0, Math.floor((termWidth - totalWidth) / 2)));
function render() {
console.clear();
if (renderHeader) {
console.log(renderHeader());
}
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("");
console.log("");
for (let row = 0; row < rows; row++) {
let line = "";
for (let col = 0; col < cols; col++) {
const idx = row * cols + col;
if (idx < items.length) {
const item = items[idx];
if (idx === current) {
line += color.cyan("[" + item.name + "]");
line += " ".repeat(colWidth - strWidth(item.name) - 2);
} else {
line += " " + item.name + " ";
line += " ".repeat(colWidth - strWidth(item.name) - 2);
}
}
}
console.log(pad + line);
let descLine = "";
for (let col = 0; col < cols; col++) {
const idx = row * cols + col;
if (idx < items.length) {
descLine += padEnd(items[idx].desc || "", colWidth);
}
}
console.log(pad + color.dim(descLine));
console.log("");
console.log("");
}
}
initKeypress();
return new Promise((resolve) => {
render();
onKey((str, key) => {
if (!key) return;
const row = Math.floor(current / cols);
const col = current % cols;
if (key.name === "up" && row > 0) {
current -= cols;
render();
} else if (key.name === "down" && row < rows - 1 && current + cols < items.length) {
current += cols;
render();
} else if (key.name === "left" && col > 0) {
current--;
render();
} else if (key.name === "right" && col < cols - 1 && current < items.length - 1) {
current++;
render();
} else if (key.name === "return") {
setImmediate(() => resolve(items[current]));
} else if (key.name === "escape" || (key.ctrl && key.name === "c")) {
process.stdin.setRawMode(false);
console.clear();
console.log(color.yellow("👋 再见!"));
process.exit(0);
}
});
});
}

18
lib/keyboard.js Normal file
View File

@ -0,0 +1,18 @@
import readline from "readline";
let initialized = false;
export function initKeypress() {
if (!initialized) {
readline.emitKeypressEvents(process.stdin);
initialized = true;
}
process.stdin.removeAllListeners("keypress");
if (process.stdin.isTTY) {
process.stdin.setRawMode(true);
}
}
export function onKey(handler) {
process.stdin.on("keypress", handler);
}

115
lib/ktx2/compressor.js Normal file
View File

@ -0,0 +1,115 @@
import fs from "fs";
import path from "path";
import { spawn } from "child_process";
import color from "picocolors";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const toktx = path.join(__dirname, "..", "..", "bin", "toktx.exe");
// 检查 toktx 是否存在
export function checkToktx() {
if (!fs.existsSync(toktx)) {
console.error("❌ 找不到 toktx.exe");
process.exit(1);
}
}
// 扫描图片文件
export function scanImages(exts) {
console.log("🔍 扫描目标文件中...");
const cwd = process.cwd();
const images = fs.readdirSync(cwd).filter(f =>
exts.some(ext => f.toLowerCase().endsWith("." + ext))
);
return { images, cwd };
}
// 构建压缩参数
export function buildArgs(input, output, config) {
const args = ["--t2"];
if (config.encoding === "uastc") {
args.push("--encode", "uastc");
const zcmpLevel = { none: "0", standard: "10", high: "18", extreme: "22" };
args.push("--zcmp", zcmpLevel[config.quality] || "10");
} else if (config.encoding === "etc1s") {
args.push("--encode", "etc1s");
} else if (config.encoding === "astc") {
args.push("--encode", "astc");
const blkSize = { none: "8x8", standard: "6x6", high: "5x5", extreme: "4x4" };
args.push("--astc_blk_d", blkSize[config.quality] || "6x6");
}
if (config.mipmap === "auto") {
args.push("--genmipmap");
}
args.push(output, input);
return args;
}
// 压缩单个文件
export function compressFile(img, config, cwd, progress) {
const baseName = img.replace(/\.[^.]+$/, "");
const out = baseName + ".ktx2";
// 点动画
let dots = 0;
const dotAnim = setInterval(() => {
const dotStr = ".".repeat(dots);
process.stdout.write(`\r${progress} ${img} 正在转换中${dotStr} `);
dots = dots >= 3 ? 0 : dots + 1;
}, 300);
process.stdout.write(`${progress} ${img} 正在转换中.`);
const args = buildArgs(img, out, config);
return new Promise((resolve) => {
const proc = spawn(toktx, args, { cwd });
let stderr = "";
proc.stderr?.on("data", data => {
stderr += data.toString();
});
proc.on("close", code => {
clearInterval(dotAnim);
if (code === 0) {
console.log(`\r${progress} ${color.green("✓")} ${out} `);
resolve({ success: true });
} else {
console.log(`\r${progress} ${color.red("✗")} ${img} 失败 `);
if (stderr) {
console.log(color.dim(` 错误: ${stderr.trim()}`));
}
resolve({ success: false, error: stderr });
}
});
proc.on("error", err => {
clearInterval(dotAnim);
console.log(`\r${progress} ${color.red("✗")} ${img} 失败 `);
console.log(color.dim(` 错误: ${err.message}`));
resolve({ success: false, error: err.message });
});
});
}
// 批量压缩
export async function compressAll(images, config, cwd) {
const total = images.length;
let finished = 0;
let failed = 0;
for (const img of images) {
finished++;
const progress = `(${finished}/${total})`;
const result = await compressFile(img, config, cwd, progress);
if (!result.success) failed++;
}
return { total, failed };
}

62
lib/ktx2/config.js Normal file
View File

@ -0,0 +1,62 @@
// 步骤配置
export const steps = [
{
name: "文件格式",
type: "multiselect",
message: "请选择要压缩的图片类型",
options: [
{ value: "png", label: "PNG (.png)(无损格式,适合图标和透明图)" },
{ value: "jpg", label: "JPG (.jpg)(有损格式,适合照片和复杂图像)" },
{ value: "jpeg", label: "JPEG (.jpeg)同JPG仅扩展名不同" },
{ value: "webp", label: "WebP (.webp)(新一代格式,体积更小)" },
{ value: "tga", label: "TGA (.tga)(游戏纹理常用格式)" }
],
default: ["png", "jpg"]
},
{
name: "压缩程度",
type: "select",
message: "请选择压缩级别",
options: [
{ value: "none", label: "无压缩(原始质量)", hint: "保持原始文件大小,图片和内容无损" },
{ value: "standard", label: "标准压缩(推荐)", hint: "平衡文件大小与质量压缩率约40%" },
{ value: "high", label: "高度压缩(最小体积)", hint: "最大程度减小文件体积,可能轻微影响清晰度" },
{ value: "extreme", label: "极限压缩(极致压缩)", hint: "牺牲部分质量换取最小体积,适合网络传输" }
],
default: "standard"
},
{
name: "编码格式",
type: "select",
message: "请选择编码格式",
options: [
{ value: "uastc", label: "UASTC通用超压缩纹理", hint: "高质量GPU纹理解码快适合实时渲染" },
{ value: "etc1s", label: "ETC1S基础压缩纹理", hint: "文件体积最小,兼容性好,适合移动端" },
{ value: "astc", label: "ASTC自适应纹理压缩", hint: "灵活块大小,质量与体积可调,适合高端设备" }
],
default: "uastc"
},
{
name: "Mipmap",
type: "select",
message: "请选择Mipmap生成方式",
options: [
{ value: "auto", label: "自动生成(推荐)", hint: "根据图片尺寸自动生成多级纹理,优化远距离渲染" },
{ value: "none", label: "不生成Mipmap", hint: "仅保留原始尺寸,文件更小但可能出现锯齿" },
{ value: "custom", label: "自定义层级", hint: "手动指定Mipmap层数精细控制纹理细节" }
],
default: "auto"
},
{
name: "输出选项",
type: "multiselect",
message: "请选择输出选项",
options: [
{ value: "overwrite", label: "覆盖已存在文件(自动替换同名文件)" },
{ value: "keepOriginal", label: "保留原文件(压缩后不删除源文件)" },
{ value: "report", label: "生成压缩报告(输出详细的压缩统计信息)" },
{ value: "silent", label: "静默模式(减少控制台输出信息)" }
],
default: ["overwrite", "keepOriginal"]
}
];

40
lib/ktx2/index.js Normal file
View File

@ -0,0 +1,40 @@
import color from "picocolors";
import { checkToktx, scanImages, compressAll } from "./compressor.js";
import { runInteractive, showSummary } from "./ui.js";
export async function run() {
// 检查 toktx
checkToktx();
// 运行交互界面
const result = await runInteractive();
// ESC 返回主菜单
if (!result) return "back";
const [exts, quality, encoding, mipmap, outputOpts] = result;
const config = { exts, quality, encoding, mipmap, outputOpts };
// 显示配置摘要
showSummary(config);
// 扫描文件
const { images, cwd } = scanImages(exts);
if (images.length === 0) {
console.log(color.yellow("当前目录没有匹配的图片"));
return;
}
console.log(`📁 找到 ${color.cyan(images.length)} 个待转换文件\n`);
// 执行压缩
const { total, failed } = await compressAll(images, config, cwd);
// 显示结果
if (failed > 0) {
console.log(color.yellow(`\n⚠️ 完成,但有 ${failed} 个文件失败`));
} else {
console.log(color.green("\n🎉 全部文件压缩完成!"));
}
}

151
lib/ktx2/ui.js Normal file
View File

@ -0,0 +1,151 @@
import color from "picocolors";
import { steps } from "./config.js";
import { initKeypress, onKey } from "../keyboard.js";
// 存储结果和状态
let results = [];
let completed = new Set();
let currentStep = 0;
let currentOption = 0;
// 初始化结果
export function initResults() {
results = steps.map(s => s.type === "multiselect" ? [...s.default] : s.default);
completed = new Set();
currentStep = 0;
currentOption = 0;
}
// 渲染导航栏
function renderNav() {
const nav = steps.map((step, i) => {
if (completed.has(i)) {
return color.green(`${step.name}`);
} else if (i === currentStep) {
return color.bgCyan(color.black(` ${step.name} `));
} else {
return color.dim(`${step.name}`);
}
});
return `${nav.join(" ")} ${color.green("✓Submit")}`;
}
// 渲染选项列表
function renderOptions() {
const step = steps[currentStep];
const lines = [];
lines.push(color.cyan(step.message));
lines.push("");
step.options.forEach((opt, i) => {
const isCurrent = i === currentOption;
const isSelected = step.type === "multiselect"
? results[currentStep].includes(opt.value)
: results[currentStep] === opt.value;
let prefix;
if (step.type === "multiselect") {
prefix = isSelected ? color.green("◉ ") : "○ ";
} else {
prefix = isSelected ? color.green("● ") : "○ ";
}
const cursor = isCurrent ? color.cyan(" ") : " ";
const label = isCurrent ? color.cyan(opt.label) : opt.label;
const check = isSelected ? color.green(" ✓") : "";
lines.push(`${cursor}${prefix}${label}${check}`);
if (opt.hint) {
lines.push(` ${color.dim(opt.hint)}`);
}
});
return lines.join("\n");
}
// 渲染整个界面
function render() {
console.clear();
console.log(color.bgCyan(color.black(" KTX2 纹理压缩工具 ")));
console.log("\n" + renderNav());
console.log(color.dim("\n← → 切换步骤 | ↑ ↓ 选择 | Space 选中 | Tab 提交 | Esc 返回\n"));
console.log(renderOptions());
}
// 主交互循环
export async function runInteractive() {
initResults();
initKeypress();
return new Promise((resolve) => {
render();
onKey((str, key) => {
if (!key) return;
const step = steps[currentStep];
const optCount = step.options.length;
if (key.name === "left") {
if (currentStep > 0) {
currentStep--;
currentOption = 0;
}
render();
} else if (key.name === "right") {
if (currentStep < steps.length - 1) {
currentStep++;
currentOption = 0;
}
render();
} else if (key.name === "up") {
currentOption = (currentOption - 1 + optCount) % optCount;
render();
} else if (key.name === "down") {
currentOption = (currentOption + 1) % optCount;
render();
} else if (key.name === "space") {
const opt = step.options[currentOption];
if (step.type === "multiselect") {
const idx = results[currentStep].indexOf(opt.value);
if (idx >= 0) {
results[currentStep].splice(idx, 1);
} else {
results[currentStep].push(opt.value);
}
} else {
results[currentStep] = opt.value;
}
completed.add(currentStep);
render();
} else if (key.name === "return") {
if (step.type === "select") {
results[currentStep] = step.options[currentOption].value;
}
completed.add(currentStep);
if (currentStep < steps.length - 1) {
currentStep++;
currentOption = 0;
}
render();
} else if (key.name === "tab") {
resolve(results);
} else if (key.name === "escape" || (key.ctrl && key.name === "c")) {
resolve(null);
}
});
});
}
// 显示配置摘要
export function showSummary(config) {
console.clear();
console.log(color.bgCyan(color.black(" KTX2 纹理压缩工具 ")));
console.log("\n" + color.green("配置完成!当前设置:"));
console.log(` 文件格式: ${config.exts.join(", ")}`);
console.log(` 压缩程度: ${config.quality}`);
console.log(` 编码格式: ${config.encoding}`);
console.log(` Mipmap: ${config.mipmap}`);
console.log(` 输出选项: ${config.outputOpts.join(", ")}\n`);
}

90
lib/menu.js Normal file
View File

@ -0,0 +1,90 @@
import color from "picocolors";
import boxen from "boxen";
import figlet from "figlet";
import { gridSelect } from "./grid.js";
// 古诗配置
let poemConfig = {
lines: ["你我皆牛马", "生在人世间", "终日奔波苦", "一刻不得闲"],
perLine: 2,
padding: { top: 2, bottom: 2, left: 6, right: 6 },
borderStyle: "double",
borderColor: "cyan",
};
// 标题配置
let titleConfig = {
text: "Zguiy Tool Box",
font: "Standard",
color: "magenta",
};
// 工具列表
const tools = [
{ name: "KTX2 纹理压缩", desc: "图片转KTX2格式", module: "./lib/ktx2/index.js" },
{ name: "模型压缩", desc: "压缩glTF/GLB模型", module: "./lib/model/index.js" },
{ name: "图片批量处理", desc: "裁剪/缩放/转换", module: "./lib/image/index.js" },
{ name: "Sprite图集", desc: "合并精灵图集", module: "./lib/sprite/index.js" },
{ name: "LOD生成器", desc: "生成多级细节", module: "./lib/lod/index.js" },
{ name: "音频压缩", desc: "压缩音频文件", module: "./lib/audio/index.js" },
];
// 设置古诗
export function setPoem(lines, perLine = 2) {
poemConfig.lines = lines;
poemConfig.perLine = perLine;
}
// 设置古诗框样式
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;
}
// 渲染古诗
function renderPoem() {
const merged = [];
for (let i = 0; i < poemConfig.lines.length; i += poemConfig.perLine) {
merged.push(poemConfig.lines.slice(i, i + poemConfig.perLine).join(""));
}
return boxen(color.yellow(merged.join("\n")), {
padding: poemConfig.padding,
borderStyle: poemConfig.borderStyle,
borderColor: poemConfig.borderColor,
textAlignment: "center",
float: "center",
});
}
// 渲染标题
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 renderHeader() {
return renderPoem() + "\n\n" + renderTitle();
}
// 主菜单
export async function showMainMenu() {
return gridSelect({
items: tools,
cols: 3,
colWidth: 24,
renderHeader: renderHeader,
});
}
export { tools, poemConfig, titleConfig };

31
lib/poem.js Normal file
View File

@ -0,0 +1,31 @@
import boxen from "boxen";
import color from "picocolors";
/**
* 显示带边框的古诗(固定在顶部居中)
* @param {string[]} lines - 诗句数组
* @param {number} perLine - 每行显示几句默认2
*/
export function showPoem(lines, perLine = 2) {
// 合并诗句
const merged = [];
for (let i = 0; i < lines.length; i += perLine) {
merged.push(lines.slice(i, i + perLine).join(""));
}
const content = merged.join("\n");
console.log(
boxen(color.yellow(content), {
padding: { top: 1, bottom: 1, left: 4, right: 4 },
margin: { top: 1, bottom: 1, left: 0, right: 0 },
borderStyle: "double",
borderColor: "cyan",
textAlignment: "center",
float: "center",
})
);
}
// 默认古诗
export const defaultPoem = ["你我皆牛马", "生在人世间", "终日奔波苦", "一刻不得闲"];

View File

@ -1,5 +1,6 @@
import color from "picocolors"; import color from "picocolors";
import readline from "readline"; import readline from "readline";
import boxen from "boxen";
import { steps } from "./config.js"; import { steps } from "./config.js";
// 存储结果和状态 // 存储结果和状态
@ -8,6 +9,31 @@ let completed = new Set();
let currentStep = 0; let currentStep = 0;
let currentOption = 0; let currentOption = 0;
// 古诗配置
let poemLines = ["你我皆牛马", "生在人世间", "终日奔波苦", "一刻不得闲"];
let poemPerLine = 2;
// 设置古诗
export function setPoem(lines, perLine = 2) {
poemLines = lines;
poemPerLine = perLine;
}
// 渲染古诗
function renderPoem() {
const merged = [];
for (let i = 0; i < poemLines.length; i += poemPerLine) {
merged.push(poemLines.slice(i, i + poemPerLine).join(""));
}
return boxen(color.yellow(merged.join("\n")), {
padding: { top: 1, bottom: 1, left: 4, right: 4 },
borderStyle: "double",
borderColor: "cyan",
textAlignment: "center",
float: "center",
});
}
// 初始化结果 // 初始化结果
export function initResults() { export function initResults() {
results = steps.map(s => s.type === "multiselect" ? [...s.default] : s.default); results = steps.map(s => s.type === "multiselect" ? [...s.default] : s.default);
@ -67,6 +93,7 @@ function renderOptions() {
// 渲染整个界面 // 渲染整个界面
function render() { function render() {
console.clear(); console.clear();
console.log(renderPoem());
console.log(color.bgCyan(color.black(" KTX2 纹理压缩工具 "))); console.log(color.bgCyan(color.black(" KTX2 纹理压缩工具 ")));
console.log("\n" + renderNav()); console.log("\n" + renderNav());
console.log(color.dim("\n← → 切换步骤 | ↑ ↓ 选择选项 | Space 选中 | Enter 确认 | Tab 提交\n")); console.log(color.dim("\n← → 切换步骤 | ↑ ↓ 选择选项 | Space 选中 | Enter 确认 | Tab 提交\n"));
@ -152,6 +179,7 @@ export async function runInteractive() {
// 显示配置摘要 // 显示配置摘要
export function showSummary(config) { export function showSummary(config) {
console.clear(); console.clear();
console.log(renderPoem());
console.log(color.bgCyan(color.black(" KTX2 纹理压缩工具 "))); console.log(color.bgCyan(color.black(" KTX2 纹理压缩工具 ")));
console.log("\n" + color.green("配置完成!当前设置:")); console.log("\n" + color.green("配置完成!当前设置:"));
console.log(` 文件格式: ${config.exts.join(", ")}`); console.log(` 文件格式: ${config.exts.join(", ")}`);

261
package-lock.json generated
View File

@ -9,6 +9,8 @@
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@clack/prompts": "^0.11.0", "@clack/prompts": "^0.11.0",
"boxen": "^8.0.1",
"figlet": "^1.9.4",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"prompts": "^2.4.2" "prompts": "^2.4.2"
}, },
@ -37,6 +39,189 @@
"sisteransi": "^1.0.5" "sisteransi": "^1.0.5"
} }
}, },
"node_modules/ansi-align": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
"integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
"license": "ISC",
"dependencies": {
"string-width": "^4.1.0"
}
},
"node_modules/ansi-align/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-align/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/ansi-align/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-align/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ansi-styles": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/boxen": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz",
"integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==",
"license": "MIT",
"dependencies": {
"ansi-align": "^3.0.1",
"camelcase": "^8.0.0",
"chalk": "^5.3.0",
"cli-boxes": "^3.0.0",
"string-width": "^7.2.0",
"type-fest": "^4.21.0",
"widest-line": "^5.0.0",
"wrap-ansi": "^9.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/camelcase": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz",
"integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==",
"license": "MIT",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/chalk": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
"license": "MIT",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/cli-boxes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
"integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"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/emoji-regex": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
"integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
"license": "MIT"
},
"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/get-east-asian-width": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
"integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/kleur": { "node_modules/kleur": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
@ -70,6 +255,82 @@
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
"license": "MIT" "license": "MIT"
},
"node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^10.3.0",
"get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/strip-ansi": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/type-fest": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/widest-line": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz",
"integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==",
"license": "MIT",
"dependencies": {
"string-width": "^7.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/wrap-ansi": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
"integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.2.1",
"string-width": "^7.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
} }
} }
} }

View File

@ -9,6 +9,8 @@
}, },
"dependencies": { "dependencies": {
"@clack/prompts": "^0.11.0", "@clack/prompts": "^0.11.0",
"boxen": "^8.0.1",
"figlet": "^1.9.4",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"prompts": "^2.4.2" "prompts": "^2.4.2"
} }

7
test-poem.js Normal file
View File

@ -0,0 +1,7 @@
import { showPoem, defaultPoem } from "./lib/poem.js";
// 使用默认古诗
showPoem(defaultPoem);
// 自定义古诗
// showPoem(["床前明月光", "疑是地上霜", "举头望明月", "低头思故乡"]);