xw-devtool-cli 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2025 npmxw
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any purpose
6
+ with or without fee is hereby granted, provided that the above copyright notice
7
+ and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
11
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
13
+ OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
14
+ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
15
+ THIS SOFTWARE.
package/README.md CHANGED
@@ -1,73 +1,122 @@
1
1
  # xw-devtool-cli
2
2
 
3
- 一个上手即用的命令行工具箱,帮你快速完成常见开发小任务:URL 编解码、字符串 Base64、图片与 Base64 互转、时间格式化与当前时间戳、Mock 文本(中/英)、UUID、中文转拼音等,且工具持续添加中......,结果会自动复制到剪贴板,方便粘贴到代码或文档。
3
+ 一个基于 Node.js 的开发者命令行工具箱,旨在提供开箱即用的常用开发工具,帮助开发者快速处理日常任务。
4
4
 
5
- ## 安装
6
- - 全局安装:`npm i -g xw-devtool-cli`
7
- - 启动:在终端运行 `xw-devtool`
8
- - 免安装一次性使用:`npx xw-devtool@latest`
5
+ 主要功能包括:Base64 编解码、图片格式转换、图片与 Base64 互转、Mock 数据生成、时间戳/日期格式化、URL 编解码、UUID 生成、汉字转拼音等。所有结果均自动复制到剪贴板,极大提升开发效率。
9
6
 
10
- ## 快速开始
11
- 启动后显示主菜单,输入数字选择功能:
7
+ ## ✨ 功能特性
8
+
9
+ - **Base64 工具**:支持字符串与 Base64 互转,支持从剪贴板、文件或手动输入读取。
10
+ - **图片工具**:
11
+ - **图片格式转换**:支持 PNG、JPG、WebP 格式互转,可调整压缩质量。
12
+ - **图片 ↔ Base64**:支持图片转 Base64 字符串,以及 Base64 还原为图片文件。
13
+ - **Mock 数据生成**:
14
+ - 支持生成:英文段落 (Lorem Ipsum)、中文字符、中国居民身份证号、电子邮箱、URL、订单号、手机号、座机号。
15
+ - 支持批量生成。
16
+ - **时间工具**:
17
+ - **时间格式化**:时间戳/日期字符串 -> `YYYY-MM-DD HH:mm:ss`。
18
+ - **获取时间戳**:快速获取当前毫秒级时间戳。
19
+ - **其他工具**:
20
+ - **URL 编解码**:Encode/Decode URL。
21
+ - **UUID**:生成 UUID v4。
22
+ - **中文转拼音**:将汉字转换为不带声调的拼音。
23
+ - **便捷操作**:
24
+ - 支持文件选择对话框 (Windows)。
25
+ - 结果自动复制到剪贴板。
26
+ - 支持将大文本结果保存为文件。
27
+
28
+ ## 📦 安装
29
+
30
+ ### 全局安装 (推荐)
31
+ ```bash
32
+ npm install -g xw-devtool-cli
12
33
  ```
13
- 1. URL Encode/Decode
14
- 2. String Encode/Decode (Base64)
15
- 3. Image <-> Base64
16
- 4. Time Format
17
- 5. Get Current Timestamp
18
- 6. Mock Text
19
- 7. Get UUID
20
- 8. Chinese to Pinyin
21
- 0. Exit
34
+ 安装后即可在终端直接使用 `xw-devtool` 命令。
35
+
36
+ ### 免安装运行 (npx)
37
+ ```bash
38
+ npx xw-devtool-cli@latest
22
39
  ```
23
40
 
24
- ## 常用操作
25
- - URL 编解码
26
- - 选择 `1` → 选择 `Encode` 或 `Decode` → 输入 URL → 结果自动复制
27
- - 字符串 Base64
28
- - 选择 `2` → 选择 `Encode` 或 `Decode`
29
- - 选择输入来源:`Clipboard`(推荐长文本)/ `File` / `Manual input`
30
- - 选择输出方式:`Copy to clipboard` / `Save to file` / `Preview + copy`
31
- - 保存文件会给出默认文件名:`base64-[encode|decode]-<timestamp>.txt`
32
- - 图片 ↔ Base64
33
- - 图片 → Base64:选择 `3` → 选择 `Image -> Base64` → 选择图片(对话框或手动路径)→ 选择输出方式(复制或保存为 `.txt`)
34
- - Base64 图片:选择 `3` → 选择 `Base64 -> Image` → 选择输入来源(剪贴板、文件或手动输入)→ 自动识别 `data:image/*` 前缀并推断扩展名 保存图片(默认名:`image-<timestamp>.<ext>`)
35
- - 时间工具
36
- - 时间格式化:选择 `4` → 输入时间戳(秒/毫秒)或日期字符串,或直接回车使用当前时间 → 输出 `YYYY-MM-DD HH:mm:ss`
37
- - 当前时间戳:选择 `5` → 显示毫秒级时间戳并复制
38
- - Mock 文本
39
- - 选择 `6` → 选择语言(英文/中文)
40
- - 英文:输入段落数生成 Lorem Ipsum
41
- - 中文:输入字数生成随机常用汉字
42
- - UUID
43
- - 选择 `7` → 生成 UUID v4 并复制
44
- - 中文转拼音
45
- - 选择 `8` → 输入中文 → 输出不带声调的拼音(空格分词)
46
-
47
- ## 输入与输出
48
- - 输入来源
49
- - `Clipboard`:直接读取剪贴板(适合非常长的内容)
50
- - `File`:支持文件对话框或手动输入路径
51
- - `Manual input`:命令行输入(适合短文本)
52
- - 文件输入方式(当选择 `File`)
53
- - `Select file (dialog)`:弹出系统文件选择框(Windows)
54
- - `Enter file path manually`:手动输入路径
55
- - 输出方式
56
- - `Copy to clipboard`:不打印全文,直接复制
57
- - `Save to file`:弹出保存对话框(Windows),或手动输入路径;默认文件名为“工具名 + 时间戳”
58
- - `Preview + copy`:显示长度与头尾少量字符预览,并复制完整结果
59
-
60
- ## 提示
61
- - 粘贴超长文本到控制台会很慢,建议使用 `Clipboard` 或 `File` 作为输入来源,并使用 `Save to file` 或 `Preview + copy` 作为输出方式
62
- - 在任何步骤按 `Ctrl+C` 会优雅退出并显示 `Bye!`
63
- - 工具在本地离线运行,不依赖网络
64
-
65
- ## 系统要求
66
- - Node.js `>= 18`
67
- - Windows:支持文件选择/保存对话框
68
- - macOS/Linux:使用手动路径输入(可按需扩展对话框支持)
69
-
70
- ## 卸载
71
- - 全局卸载:`npm uninstall -g xw-devtool-cli`
41
+ ## 🚀 快速开始
42
+
43
+ 在终端运行:
44
+ ```bash
45
+ xw-devtool
46
+ ```
47
+
48
+ 启动后将显示交互式菜单,通过键盘方向键或输入数字选择功能:
49
+
50
+ ```text
51
+ ? Select a tool: (Use arrow keys)
52
+ > 1. URL Encode/Decode
53
+ 2. String Encode/Decode (Base64)
54
+ 3. Image <-> Base64
55
+ 4. Image Format Convert
56
+ 5. Time Format
57
+ 6. Get Current Timestamp
58
+ 7. Mock Text
59
+ 8. Get UUID
60
+ 9. Chinese to Pinyin
61
+ 0. Exit
62
+ ```
63
+
64
+ ## 📖 详细使用指南
65
+
66
+ ### 1. URL 编解码
67
+ - 选择 `1` 进入。
68
+ - 选择 `Encode` (编码) 或 `Decode` (解码)。
69
+ - 输入 URL 字符串,结果自动复制。
70
+
71
+ ### 2. 字符串 Base64 转换
72
+ - 选择 `2` 进入。
73
+ - 支持三种输入源:
74
+ - **Clipboard**: 直接读取剪贴板内容(适合处理长文本)。
75
+ - **File**: 读取文本文件内容。
76
+ - **Manual input**: 手动粘贴或输入文本。
77
+ - 输出支持:直接复制、保存为文件、预览后复制。
78
+
79
+ ### 3. 图片与 Base64 互转
80
+ - 选择 `3` 进入。
81
+ - **Image -> Base64**:
82
+ - 选择图片文件(支持对话框选择)。
83
+ - 输出 Base64 字符串(可保存为 `.txt` 文件防止控制台卡顿)。
84
+ - **Base64 -> Image**:
85
+ - 输入 Base64 字符串(支持从文件读取或剪贴板读取)。
86
+ - 自动识别图片格式并保存为文件。
87
+
88
+ ### 4. 图片格式转换
89
+ - 选择 `4` 进入。
90
+ - 选择源图片文件。
91
+ - 选择目标格式 (PNG / JPG / WebP)。
92
+ - 设置压缩参数(如 JPG 质量 1-100,PNG 压缩等级 0-9)。
93
+ - 生成的新图片将保存在源文件同级目录。
94
+
95
+ ### 5. Mock 数据生成
96
+ - 选择 `7` 进入。
97
+ - 提供多种数据类型:
98
+ - **英文段落**: 生成 Lorem Ipsum 文本。
99
+ - **中文字符**: 生成随机常用汉字。
100
+ - **身份证号**: 生成符合规则的虚拟身份证号 (包含校验位)。
101
+ - **邮箱**: 生成随机电子邮箱地址。
102
+ - **URL**: 生成随机网址。
103
+ - **订单号**: 生成基于时间戳的模拟订单号。
104
+ - **手机号**: 生成常见号段的手机号。
105
+ - **座机号**: 生成带区号的座机号码。
106
+ - 支持指定生成数量(批量生成)。
107
+
108
+ ### 6. 时间工具
109
+ - **时间格式化 (5)**: 输入时间戳(秒/毫秒)或日期字符串,输出标准化格式。留空直接回车则使用当前时间。
110
+ - **当前时间戳 (6)**: 快速获取当前毫秒时间戳。
111
+
112
+ ### 7. 其他
113
+ - **UUID (8)**: 生成一个 UUID v4。
114
+ - **中文转拼音 (9)**: 输入中文文本,输出拼音字符串(空格分隔)。
72
115
 
116
+ ## 💻 系统要求
117
+ - Node.js >= 18.0.0
118
+ - Windows 用户可享受原生文件选择/保存对话框体验。
119
+ - macOS/Linux 用户支持完整功能,但文件路径需手动输入。
73
120
 
121
+ ## 📄 License
122
+ ISC
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xw-devtool-cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "description": "基于node的开发者助手cli",
6
6
  "main": "index.js",
@@ -27,7 +27,10 @@
27
27
  "mock",
28
28
  "timestamp",
29
29
  "time",
30
- "image"
30
+ "image",
31
+ "webp",
32
+ "jpg",
33
+ "png"
31
34
  ],
32
35
  "author": "npmxw",
33
36
  "license": "ISC",
@@ -36,10 +39,10 @@
36
39
  },
37
40
  "repository": {
38
41
  "type": "git",
39
- "url": "git+https://example.com/xw-devtool-cli.git"
42
+ "url": "https://gitee.com/github-9819409/xw-devtool-cli.git"
40
43
  },
41
44
  "bugs": {
42
- "url": "https://example.com/xw-devtool-cli/issues"
45
+ "url": "https://gitee.com/github-9819409/xw-devtool-cli/issues"
43
46
  },
44
47
  "dependencies": {
45
48
  "clipboardy": "^5.0.2",
@@ -48,6 +51,7 @@
48
51
  "inquirer": "^13.1.0",
49
52
  "lorem-ipsum": "^2.0.8",
50
53
  "pinyin": "^4.0.0",
54
+ "sharp": "^0.33.5",
51
55
  "uuid": "^13.0.0"
52
56
  }
53
57
  }
@@ -0,0 +1,112 @@
1
+ import inquirer from 'inquirer';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import sharp from 'sharp';
5
+ import { selectFromMenu } from '../utils/menu.js';
6
+ import { selectFile, saveFile } from '../utils/fileDialog.js';
7
+ import { defaultFileName } from '../utils/output.js';
8
+
9
+ function getExt(p) {
10
+ const ext = path.extname(p).toLowerCase().replace('.', '');
11
+ if (ext === 'jpeg') return 'jpg';
12
+ return ext;
13
+ }
14
+
15
+ function suggestOutputPath(inputPath, targetExt) {
16
+ const dir = path.dirname(inputPath);
17
+ const base = path.basename(inputPath, path.extname(inputPath));
18
+ return path.join(dir, `${base}.${targetExt}`);
19
+ }
20
+
21
+ async function pickInputFile() {
22
+ const method = await selectFromMenu('File input method', [
23
+ { name: 'Select file (dialog)', value: 'dialog' },
24
+ { name: 'Enter file path manually', value: 'manual' }
25
+ ]);
26
+ let filePath = '';
27
+ if (method === 'dialog') {
28
+ filePath = selectFile('Image Files|*.png;*.jpg;*.jpeg;*.webp|All Files|*.*') || '';
29
+ if (!filePath) {
30
+ console.log('File dialog not available or canceled. Please enter path manually.');
31
+ }
32
+ }
33
+ if (!filePath) {
34
+ const ans = await inquirer.prompt([
35
+ {
36
+ type: 'input',
37
+ name: 'filePath',
38
+ message: 'Enter image file path:',
39
+ validate: (input) => (fs.existsSync(input) ? true : 'File does not exist.')
40
+ }
41
+ ]);
42
+ filePath = ans.filePath;
43
+ }
44
+ return filePath;
45
+ }
46
+
47
+ async function pickTargetFormat(currentExt) {
48
+ const options = [
49
+ { name: 'png', value: 'png' },
50
+ { name: 'jpg', value: 'jpg' },
51
+ { name: 'webp', value: 'webp' }
52
+ ].filter(o => o.value !== currentExt);
53
+ return await selectFromMenu('Target format', options);
54
+ }
55
+
56
+ async function pickQuality(targetExt) {
57
+ if (targetExt === 'png') {
58
+ const { level } = await inquirer.prompt([
59
+ { type: 'number', name: 'level', message: 'PNG compression level (0-9):', default: 6, validate: n => (n >= 0 && n <= 9) ? true : '0-9' }
60
+ ]);
61
+ return { pngCompressionLevel: level };
62
+ } else if (targetExt === 'jpg') {
63
+ const { quality } = await inquirer.prompt([
64
+ { type: 'number', name: 'quality', message: 'JPEG quality (1-100):', default: 80, validate: n => (n >= 1 && n <= 100) ? true : '1-100' }
65
+ ]);
66
+ return { jpegQuality: quality };
67
+ } else if (targetExt === 'webp') {
68
+ const { quality } = await inquirer.prompt([
69
+ { type: 'number', name: 'quality', message: 'WebP quality (1-100):', default: 80, validate: n => (n >= 1 && n <= 100) ? true : '1-100' }
70
+ ]);
71
+ return { webpQuality: quality };
72
+ }
73
+ return {};
74
+ }
75
+
76
+ export async function imgConvertHandler() {
77
+ const inputPath = await pickInputFile();
78
+ const inputExt = getExt(inputPath);
79
+ console.log(`Input: ${inputPath}`);
80
+
81
+ const targetExt = await pickTargetFormat(inputExt);
82
+ console.log(`Target format: ${targetExt}`);
83
+ const opts = await pickQuality(targetExt);
84
+
85
+ let outPath = saveFile(suggestOutputPath(inputPath, targetExt), 'Image Files|*.png;*.jpg;*.jpeg;*.webp|All Files|*.*');
86
+ if (!outPath) {
87
+ const def = suggestOutputPath(inputPath, targetExt);
88
+ const ans = await inquirer.prompt([
89
+ { type: 'input', name: 'outPath', message: 'Enter output file path:', default: def }
90
+ ]);
91
+ outPath = ans.outPath;
92
+ }
93
+
94
+ try {
95
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
96
+ let pipeline = sharp(inputPath);
97
+ if (targetExt === 'png') {
98
+ pipeline = pipeline.png({ compressionLevel: opts.pngCompressionLevel ?? 6 });
99
+ } else if (targetExt === 'jpg') {
100
+ pipeline = pipeline.jpeg({ quality: opts.jpegQuality ?? 80 });
101
+ if (inputExt !== 'jpg') {
102
+ pipeline = pipeline.flatten({ background: { r: 255, g: 255, b: 255 } });
103
+ }
104
+ } else if (targetExt === 'webp') {
105
+ pipeline = pipeline.webp({ quality: opts.webpQuality ?? 80 });
106
+ }
107
+ await pipeline.toFile(outPath);
108
+ console.log(`Converted: ${inputPath} -> ${outPath}`);
109
+ } catch (e) {
110
+ console.error('Conversion error:', e.message);
111
+ }
112
+ }
@@ -1,5 +1,6 @@
1
1
  import inquirer from 'inquirer';
2
2
  import { LoremIpsum } from 'lorem-ipsum';
3
+ import dayjs from 'dayjs';
3
4
  import { copy } from '../utils/clipboard.js';
4
5
  import { selectFromMenu } from '../utils/menu.js';
5
6
 
@@ -15,7 +16,6 @@ const lorem = new LoremIpsum({
15
16
  });
16
17
 
17
18
  function generateChinese(length) {
18
- // Common Chinese characters (simplified)
19
19
  const commonChars = "的一是在不了有和人这中大为上个国我以要他时来用们生到作地于出就分对成会可主发年动同工也能下过子说产种面而方后多定行学法所民得经十三之进着等部度家电力里如水化高自二理起小物现实加量都两体制机当使点从业本去把性好应开它合还因由其些然前外天政四日那社义事平形相全表间样与关各重新线内数角路最题验打果指气流接南情场变由规德问展七九几欲问无程和气光料村员真眼体别";
20
20
  let result = '';
21
21
  for (let i = 0; i < length; i++) {
@@ -25,10 +25,115 @@ function generateChinese(length) {
25
25
  return result;
26
26
  }
27
27
 
28
+ function randomInt(min, max) {
29
+ return Math.floor(Math.random() * (max - min + 1)) + min;
30
+ }
31
+
32
+ function padNumber(num, len) {
33
+ return String(num).padStart(len, '0');
34
+ }
35
+
36
+ export function generateChineseID() {
37
+ const areas = ['110101', '110102', '120101', '310101', '440104', '440305', '320102', '330106', '500101', '210102'];
38
+ const area = areas[randomInt(0, areas.length - 1)];
39
+ const start = new Date(1960, 0, 1).getTime();
40
+ const end = new Date(2010, 11, 31).getTime();
41
+ const ts = start + Math.floor(Math.random() * (end - start));
42
+ const date = dayjs(ts).format('YYYYMMDD');
43
+ const seq = padNumber(randomInt(0, 999), 3);
44
+ const base = area + date + seq;
45
+ const weights = [7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2];
46
+ const map = ['1','0','X','9','8','7','6','5','4','3','2'];
47
+ let sum = 0;
48
+ for (let i = 0; i < 17; i++) {
49
+ sum += parseInt(base[i], 10) * weights[i];
50
+ }
51
+ const check = map[sum % 11];
52
+ return base + check;
53
+ }
54
+
55
+ export function generateEmail() {
56
+ const letters = 'abcdefghijklmnopqrstuvwxyz';
57
+ const lettersDigits = letters + '0123456789';
58
+ let user = letters[randomInt(0, letters.length - 1)];
59
+ const userLen = randomInt(5, 12);
60
+ for (let i = 0; i < userLen; i++) {
61
+ const pool = Math.random() < 0.1 ? '._' : lettersDigits;
62
+ user += pool[randomInt(0, pool.length - 1)];
63
+ }
64
+ const domains = ['gmail.com', 'outlook.com', 'yahoo.com', 'qq.com', '163.com', 'icloud.com', 'example.com'];
65
+ const domain = domains[randomInt(0, domains.length - 1)];
66
+ return `${user}@${domain}`;
67
+ }
68
+
69
+ export function generateURL() {
70
+ const proto = Math.random() < 0.8 ? 'https' : 'http';
71
+ const letters = 'abcdefghijklmnopqrstuvwxyz';
72
+ const tlds = ['com', 'net', 'org', 'cn', 'io', 'dev'];
73
+ const labelLen = randomInt(5, 10);
74
+ let label = '';
75
+ for (let i = 0; i < labelLen; i++) {
76
+ label += letters[randomInt(0, letters.length - 1)];
77
+ }
78
+ const tld = tlds[randomInt(0, tlds.length - 1)];
79
+ const pathSegCount = randomInt(0, 3);
80
+ let path = '';
81
+ for (let i = 0; i < pathSegCount; i++) {
82
+ let seg = '';
83
+ const segLen = randomInt(3, 8);
84
+ for (let j = 0; j < segLen; j++) {
85
+ seg += letters[randomInt(0, letters.length - 1)];
86
+ }
87
+ path += `/${seg}`;
88
+ }
89
+ const withQuery = Math.random() < 0.4;
90
+ let query = '';
91
+ if (withQuery) {
92
+ const k = letters.substring(0, randomInt(3, 6));
93
+ const v = letters.substring(0, randomInt(3, 6));
94
+ query = `?${k}=${v}`;
95
+ }
96
+ const host = Math.random() < 0.7 ? `www.${label}.${tld}` : `${label}.${tld}`;
97
+ return `${proto}://${host}${path}${query}`;
98
+ }
99
+
100
+ export function generateOrderNo() {
101
+ const ts = dayjs().format('YYYYMMDDHHmmss');
102
+ const rand = padNumber(randomInt(0, 999999), 6);
103
+ return `ORD${ts}${rand}`;
104
+ }
105
+
106
+ export function generateMobile() {
107
+ const prefixes = ['130','131','132','133','134','135','136','137','138','139','150','151','152','153','155','156','157','158','159','170','171','172','173','175','176','177','178','180','181','182','183','185','186','187','188','189','198','199','166'];
108
+ const p = prefixes[randomInt(0, prefixes.length - 1)];
109
+ let rest = '';
110
+ for (let i = 0; i < 8; i++) {
111
+ rest += String(randomInt(0, 9));
112
+ }
113
+ return `${p}${rest}`;
114
+ }
115
+
116
+ export function generateLandline() {
117
+ const areas = ['010','020','021','022','023','024','025','027','028','029','0311','0371','0531','0755','0571'];
118
+ const area = areas[randomInt(0, areas.length - 1)];
119
+ const len = randomInt(7, 8);
120
+ let num = '';
121
+ for (let i = 0; i < len; i++) {
122
+ num += String(randomInt(0, 9));
123
+ }
124
+ return `${area}-${num}`;
125
+ }
126
+
28
127
  export async function mockHandler() {
29
- const type = await selectFromMenu('Mock Text', [
30
- { name: 'English', value: 'en' },
31
- { name: 'Chinese', value: 'cn' }
128
+ const type = await selectFromMenu('Mock Data', [
129
+ { name: '英文段落', value: 'en' },
130
+ { name: '中文字符', value: 'cn' },
131
+ { name: '身份证号', value: 'id' },
132
+ { name: '邮箱', value: 'email' },
133
+ { name: 'URL', value: 'url' },
134
+ { name: '订单号', value: 'order' },
135
+ { name: '手机号', value: 'mobile' },
136
+ { name: '座机号', value: 'landline' }
32
137
  ]);
33
138
 
34
139
  let result = '';
@@ -43,7 +148,7 @@ export async function mockHandler() {
43
148
  }
44
149
  ]);
45
150
  result = lorem.generateParagraphs(count);
46
- } else {
151
+ } else if (type === 'cn') {
47
152
  const { count } = await inquirer.prompt([
48
153
  {
49
154
  type: 'number',
@@ -53,6 +158,48 @@ export async function mockHandler() {
53
158
  }
54
159
  ]);
55
160
  result = generateChinese(count);
161
+ } else if (type === 'id') {
162
+ const { count } = await inquirer.prompt([
163
+ { type: 'number', name: 'count', message: 'Generate how many?', default: 1 }
164
+ ]);
165
+ const arr = [];
166
+ for (let i = 0; i < count; i++) arr.push(generateChineseID());
167
+ result = arr.join('\n');
168
+ } else if (type === 'email') {
169
+ const { count } = await inquirer.prompt([
170
+ { type: 'number', name: 'count', message: 'Generate how many?', default: 1 }
171
+ ]);
172
+ const arr = [];
173
+ for (let i = 0; i < count; i++) arr.push(generateEmail());
174
+ result = arr.join('\n');
175
+ } else if (type === 'url') {
176
+ const { count } = await inquirer.prompt([
177
+ { type: 'number', name: 'count', message: 'Generate how many?', default: 1 }
178
+ ]);
179
+ const arr = [];
180
+ for (let i = 0; i < count; i++) arr.push(generateURL());
181
+ result = arr.join('\n');
182
+ } else if (type === 'order') {
183
+ const { count } = await inquirer.prompt([
184
+ { type: 'number', name: 'count', message: 'Generate how many?', default: 1 }
185
+ ]);
186
+ const arr = [];
187
+ for (let i = 0; i < count; i++) arr.push(generateOrderNo());
188
+ result = arr.join('\n');
189
+ } else if (type === 'mobile') {
190
+ const { count } = await inquirer.prompt([
191
+ { type: 'number', name: 'count', message: 'Generate how many?', default: 1 }
192
+ ]);
193
+ const arr = [];
194
+ for (let i = 0; i < count; i++) arr.push(generateMobile());
195
+ result = arr.join('\n');
196
+ } else if (type === 'landline') {
197
+ const { count } = await inquirer.prompt([
198
+ { type: 'number', name: 'count', message: 'Generate how many?', default: 1 }
199
+ ]);
200
+ const arr = [];
201
+ for (let i = 0; i < count; i++) arr.push(generateLandline());
202
+ result = arr.join('\n');
56
203
  }
57
204
 
58
205
  console.log(`\nResult:\n${result}\n`);
package/src/index.js CHANGED
@@ -3,6 +3,7 @@ import { program } from 'commander';
3
3
  import { urlHandler } from './commands/url.js';
4
4
  import { base64Handler } from './commands/base64.js';
5
5
  import { imgBase64Handler } from './commands/imgBase64.js';
6
+ import { imgConvertHandler } from './commands/imgConvert.js';
6
7
  import { timeFormatHandler } from './commands/timeFormat.js';
7
8
  import { timestampHandler } from './commands/timestamp.js';
8
9
  import { mockHandler } from './commands/mock.js';
@@ -28,6 +29,7 @@ const features = [
28
29
  { name: 'URL Encode/Decode', value: 'url' },
29
30
  { name: 'String Encode/Decode (Base64)', value: 'base64' },
30
31
  { name: 'Image <-> Base64', value: 'imgBase64' },
32
+ { name: 'Image Format Convert', value: 'imgConvert' },
31
33
  { name: 'Time Format', value: 'timeFormat' },
32
34
  { name: 'Get Current Timestamp', value: 'timestamp' },
33
35
  { name: 'Mock Text', value: 'mock' },
@@ -102,6 +104,9 @@ async function handleAction(action) {
102
104
  case 'imgBase64':
103
105
  await imgBase64Handler();
104
106
  break;
107
+ case 'imgConvert':
108
+ await imgConvertHandler();
109
+ break;
105
110
  case 'timeFormat':
106
111
  await timeFormatHandler();
107
112
  break;
@@ -7,9 +7,10 @@ function isWindows() {
7
7
  export function selectFile(filter = 'All Files|*.*') {
8
8
  if (!isWindows()) return null;
9
9
  try {
10
- const cmd = `powershell -NoProfile -Command "Add-Type -AssemblyName System.Windows.Forms; $fd = New-Object System.Windows.Forms.OpenFileDialog; $fd.Filter='${filter.replace(/"/g, '\\"')}'; $fd.Multiselect=$false; if($fd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK){ Write-Output $fd.FileName }"`;
11
- const output = execSync(cmd, { stdio: ['pipe', 'pipe', 'ignore'] }).toString().trim();
12
- return output || null;
10
+ const cmd = `powershell -NoProfile -Command "Add-Type -AssemblyName System.Windows.Forms; $fd = New-Object System.Windows.Forms.OpenFileDialog; $fd.Filter='${filter.replace(/"/g, '\\"')}'; $fd.Multiselect=$false; if($fd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK){ $bytes = [System.Text.Encoding]::UTF8.GetBytes($fd.FileName); Write-Output ([Convert]::ToBase64String($bytes)) }"`;
11
+ const buf = execSync(cmd, { stdio: ['pipe', 'pipe', 'ignore'] });
12
+ const output = Buffer.isBuffer(buf) ? buf.toString('utf8').trim() : String(buf).trim();
13
+ return output ? Buffer.from(output, 'base64').toString('utf8') : null;
13
14
  } catch {
14
15
  return null;
15
16
  }
@@ -18,11 +19,11 @@ export function selectFile(filter = 'All Files|*.*') {
18
19
  export function saveFile(defaultName = 'output.txt', filter = 'All Files|*.*') {
19
20
  if (!isWindows()) return null;
20
21
  try {
21
- const cmd = `powershell -NoProfile -Command "Add-Type -AssemblyName System.Windows.Forms; $sd = New-Object System.Windows.Forms.SaveFileDialog; $sd.Filter='${filter.replace(/"/g, '\\"')}'; $sd.FileName='${defaultName.replace(/"/g, '\\"')}'; if($sd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK){ Write-Output $sd.FileName }"`;
22
- const output = execSync(cmd, { stdio: ['pipe', 'pipe', 'ignore'] }).toString().trim();
23
- return output || null;
22
+ const cmd = `powershell -NoProfile -Command "Add-Type -AssemblyName System.Windows.Forms; $sd = New-Object System.Windows.Forms.SaveFileDialog; $sd.Filter='${filter.replace(/"/g, '\\"')}'; $sd.FileName='${defaultName.replace(/"/g, '\\"')}'; if($sd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK){ $bytes = [System.Text.Encoding]::UTF8.GetBytes($sd.FileName); Write-Output ([Convert]::ToBase64String($bytes)) }"`;
23
+ const buf = execSync(cmd, { stdio: ['pipe', 'pipe', 'ignore'] });
24
+ const output = Buffer.isBuffer(buf) ? buf.toString('utf8').trim() : String(buf).trim();
25
+ return output ? Buffer.from(output, 'base64').toString('utf8') : null;
24
26
  } catch {
25
27
  return null;
26
28
  }
27
29
  }
28
-