xw-devtool-cli 1.0.12 → 1.0.14
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/README.md +53 -24
- package/package.json +1 -1
- package/src/commands/jsonFormat.js +83 -0
- package/src/commands/timeCalc.js +151 -0
- package/src/index.js +10 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
一个基于 Node.js 的开发者命令行工具箱,旨在提供开箱即用的常用开发工具,帮助开发者快速处理日常任务。
|
|
4
4
|
|
|
5
|
-
主要功能包括:Base64 编解码、图片格式转换、图片与 Base64 互转、Mock
|
|
5
|
+
主要功能包括:Base64 编解码、图片格式转换、图片与 Base64 互转、Mock 数据生成、时间戳/日期格式化、时间计算、URL 编解码、UUID 生成、汉字转拼音、颜色转换、变量格式转换、哈希计算、二维码生成、特殊符号大全等。所有结果均自动复制到剪贴板,极大提升开发效率。
|
|
6
6
|
|
|
7
7
|
## ✨ 功能特性
|
|
8
8
|
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
- **时间工具**:
|
|
18
18
|
- **时间格式化**:时间戳/日期字符串 -> `YYYY-MM-DD HH:mm:ss`。
|
|
19
19
|
- **获取时间戳**:快速获取当前毫秒级时间戳。
|
|
20
|
+
- **时间计算**:计算日期差值或日期偏移 (Add/Subtract)。
|
|
20
21
|
- **开发辅助工具**:
|
|
21
22
|
- **URL 编解码**:Encode/Decode URL。
|
|
22
23
|
- **Unicode 编解码**:文本与 Unicode 转义序列 (\uXXXX) 互转。
|
|
@@ -71,14 +72,16 @@ xw-devtool
|
|
|
71
72
|
7. Unicode Encode/Decode
|
|
72
73
|
8. HTML Entity Encode/Decode
|
|
73
74
|
9. Variable Format Converter
|
|
74
|
-
a.
|
|
75
|
-
b.
|
|
76
|
-
c.
|
|
77
|
-
d.
|
|
78
|
-
e.
|
|
79
|
-
f.
|
|
80
|
-
g.
|
|
81
|
-
h.
|
|
75
|
+
a. JSON Format (Minify/Prettify)
|
|
76
|
+
b. Chinese to Pinyin
|
|
77
|
+
c. Time Format / Timestamp
|
|
78
|
+
d. Time Calculation (Diff/Offset)
|
|
79
|
+
e. Color Converter (Hex <-> RGB)
|
|
80
|
+
f. Get UUID
|
|
81
|
+
g. Hash Calculator (MD5/SHA/SM3)
|
|
82
|
+
h. Mock Text
|
|
83
|
+
i. Special Characters (Symbols)
|
|
84
|
+
j. Emoji Picker
|
|
82
85
|
0. Exit
|
|
83
86
|
=================================
|
|
84
87
|
```
|
|
@@ -163,42 +166,68 @@ h. Emoji Picker
|
|
|
163
166
|
- 自动展示 CamelCase, PascalCase, SnakeCase, KebabCase, ConstantCase 五种格式。
|
|
164
167
|
- 选择一种格式自动复制。
|
|
165
168
|
|
|
166
|
-
### 10.
|
|
169
|
+
### 10. JSON 格式化
|
|
167
170
|
- 选择 `a` 进入。
|
|
171
|
+
- 支持三种模式:
|
|
172
|
+
- **Minify**: 压缩 JSON (去除空格和换行)。
|
|
173
|
+
- **Prettify (2 spaces)**: 美化 JSON (2 空格缩进)。
|
|
174
|
+
- **Prettify (4 spaces)**: 美化 JSON (4 空格缩进)。
|
|
175
|
+
- 支持自动修复简单的非标准 JSON (如 key 缺少引号的 JS 对象)。
|
|
176
|
+
- 支持从剪贴板、文件或手动输入读取。
|
|
177
|
+
- **注意**:为防止长文本刷屏,结果**仅自动复制到剪贴板**,不会在终端打印。
|
|
178
|
+
|
|
179
|
+
### 11. 中文转拼音
|
|
180
|
+
- 选择 `b` 进入。
|
|
168
181
|
- 输入中文字符串(支持直接回车读取剪贴板)。
|
|
169
182
|
- 输出对应的拼音(无声调)。
|
|
170
183
|
|
|
171
|
-
###
|
|
172
|
-
- 选择 `
|
|
184
|
+
### 12. 时间格式化 (Time Format)
|
|
185
|
+
- 选择 `c` 进入。
|
|
173
186
|
- 支持时间戳转日期字符串,或日期字符串转时间戳。
|
|
174
187
|
- 直接回车可获取当前时间戳。
|
|
175
188
|
|
|
176
|
-
###
|
|
177
|
-
- 选择 `
|
|
189
|
+
### 13. 时间计算 (Time Calculation)
|
|
190
|
+
- 选择 `d` 进入。
|
|
191
|
+
- **Diff**: 计算两个日期之间的时间差 (天、小时、分、秒)。
|
|
192
|
+
- **操作步骤**:
|
|
193
|
+
1. 输入 **开始日期** (支持 `YYYY-MM-DD HH:mm:ss` 等多种格式,默认为当前时间)。
|
|
194
|
+
2. 输入 **结束日期** (默认为当前时间)。
|
|
195
|
+
3. 查看时间差结果 (显示总天数、总小时数及详细的 `x天x小时x分x秒`)。
|
|
196
|
+
- **Offset**: 计算指定偏移量后的日期。
|
|
197
|
+
- **操作步骤**:
|
|
198
|
+
1. 输入 **基准日期** (默认为当前时间)。
|
|
199
|
+
2. 选择 **时间单位** (Days, Hours, Minutes, Seconds, etc.)。
|
|
200
|
+
3. 选择 **操作类型** (Add / Subtract)。
|
|
201
|
+
4. 输入 **数量** (Amount)。
|
|
202
|
+
5. 查看计算后的新日期。
|
|
203
|
+
- 示例:计算 "当前时间 3 天后" 或 "2025-01-01 5 小时前"。
|
|
204
|
+
|
|
205
|
+
### 14. 颜色转换 (Hex <-> RGB)
|
|
206
|
+
- 选择 `e` 进入。
|
|
178
207
|
- 输入 Hex 颜色值 (如 #333) 或 RGB 值 (如 rgb(51,51,51))。
|
|
179
208
|
- 自动转换并显示对应格式。
|
|
180
209
|
|
|
181
|
-
###
|
|
182
|
-
- 选择 `
|
|
210
|
+
### 15. Get UUID
|
|
211
|
+
- 选择 `f` 进入。
|
|
183
212
|
- 自动生成一个 UUID v4 并复制到剪贴板。
|
|
184
213
|
|
|
185
|
-
###
|
|
186
|
-
- 选择 `
|
|
214
|
+
### 16. 哈希计算 (Encryption)
|
|
215
|
+
- 选择 `g` 进入。
|
|
187
216
|
- 输入文本(支持直接回车读取剪贴板)。
|
|
188
217
|
- 同时计算并显示 MD5, SHA1, SHA256, SHA512, SM3 哈希值。
|
|
189
218
|
|
|
190
|
-
###
|
|
191
|
-
- 选择 `
|
|
219
|
+
### 17. Mock 数据生成
|
|
220
|
+
- 选择 `h` 进入。
|
|
192
221
|
- 提供多种数据类型:英文段落、中文字符、身份证号、邮箱、手机号等。
|
|
193
222
|
- 支持指定生成数量。
|
|
194
223
|
|
|
195
|
-
###
|
|
196
|
-
- 选择 `
|
|
224
|
+
### 18. 特殊符号大全
|
|
225
|
+
- 选择 `i` 进入。
|
|
197
226
|
- 网格化展示常用特殊符号。
|
|
198
227
|
- 输入符号对应的编号即可一键复制。
|
|
199
228
|
|
|
200
|
-
###
|
|
201
|
-
- 选择 `
|
|
229
|
+
### 19. Emoji 输入
|
|
230
|
+
- 选择 `j` 进入。
|
|
202
231
|
- 分类展示常用 Emoji (表情、手势、动物、食物等)。
|
|
203
232
|
- 输入编号选择并复制 Emoji 到剪贴板。
|
|
204
233
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import { copy } from '../utils/clipboard.js';
|
|
3
|
+
import { selectFromMenu } from '../utils/menu.js';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
|
|
6
|
+
export async function jsonFormatHandler() {
|
|
7
|
+
const mode = await selectFromMenu('JSON Format', [
|
|
8
|
+
{ name: 'Minify (Compact)', value: 'minify' },
|
|
9
|
+
{ name: 'Prettify (2 spaces)', value: 'prettify-2' },
|
|
10
|
+
{ name: 'Prettify (4 spaces)', value: 'prettify-4' }
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
const inputSource = await selectFromMenu('Select Input Source', [
|
|
14
|
+
{ name: 'Clipboard (Paste automatically)', value: 'clipboard' },
|
|
15
|
+
{ name: 'Manual Input', value: 'manual' },
|
|
16
|
+
{ name: 'File', value: 'file' }
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
let input = '';
|
|
20
|
+
if (inputSource === 'clipboard') {
|
|
21
|
+
const clipboardy = (await import('clipboardy')).default;
|
|
22
|
+
input = await clipboardy.read();
|
|
23
|
+
} else if (inputSource === 'file') {
|
|
24
|
+
const { filePath } = await inquirer.prompt([
|
|
25
|
+
{
|
|
26
|
+
type: 'input',
|
|
27
|
+
name: 'filePath',
|
|
28
|
+
message: 'Enter file path:'
|
|
29
|
+
}
|
|
30
|
+
]);
|
|
31
|
+
try {
|
|
32
|
+
input = fs.readFileSync(filePath.replace(/"/g, ''), 'utf-8');
|
|
33
|
+
} catch (e) {
|
|
34
|
+
console.error('Error reading file:', e.message);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
const { manualInput } = await inquirer.prompt([
|
|
39
|
+
{
|
|
40
|
+
type: 'input',
|
|
41
|
+
name: 'manualInput',
|
|
42
|
+
message: 'Enter JSON string:'
|
|
43
|
+
}
|
|
44
|
+
]);
|
|
45
|
+
input = manualInput;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// Try to parse relaxed JSON (like JS objects) if possible, but standard JSON.parse is safer for strict JSON
|
|
50
|
+
// We can try to evaluate if it fails parsing, but that's risky.
|
|
51
|
+
// Let's stick to standard JSON parsing first.
|
|
52
|
+
let jsonObj;
|
|
53
|
+
try {
|
|
54
|
+
jsonObj = JSON.parse(input);
|
|
55
|
+
} catch (e) {
|
|
56
|
+
// If strict parse fails, try to be more lenient if it's a JS object string
|
|
57
|
+
// e.g. { a: 1 } instead of { "a": 1 }
|
|
58
|
+
// Using Function constructor is safer than eval but still risky.
|
|
59
|
+
// For a dev tool, it might be acceptable if warned.
|
|
60
|
+
// Let's try a simple regex fix for common cases (keys without quotes)
|
|
61
|
+
try {
|
|
62
|
+
// This is a very basic attempt to fix unquoted keys
|
|
63
|
+
const fixed = input.replace(/([{,]\s*)([a-zA-Z0-9_]+?)\s*:/g, '$1"$2":');
|
|
64
|
+
jsonObj = JSON.parse(fixed);
|
|
65
|
+
} catch (e2) {
|
|
66
|
+
throw new Error('Invalid JSON format');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let output = '';
|
|
71
|
+
if (mode === 'minify') {
|
|
72
|
+
output = JSON.stringify(jsonObj);
|
|
73
|
+
} else if (mode === 'prettify-2') {
|
|
74
|
+
output = JSON.stringify(jsonObj, null, 2);
|
|
75
|
+
} else {
|
|
76
|
+
output = JSON.stringify(jsonObj, null, 4);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
await copy(output);
|
|
80
|
+
} catch (e) {
|
|
81
|
+
console.error('Error processing JSON:', e.message);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import dayjs from 'dayjs';
|
|
3
|
+
import duration from 'dayjs/plugin/duration.js';
|
|
4
|
+
import { copy } from '../utils/clipboard.js';
|
|
5
|
+
import { selectFromMenu } from '../utils/menu.js';
|
|
6
|
+
|
|
7
|
+
dayjs.extend(duration);
|
|
8
|
+
|
|
9
|
+
export async function timeCalcHandler() {
|
|
10
|
+
const mode = await selectFromMenu('Time Calculation', [
|
|
11
|
+
{ name: 'Diff (Calculate difference between two dates)', value: 'diff' },
|
|
12
|
+
{ name: 'Add/Subtract (Calculate new date from offset)', value: 'offset' }
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
if (mode === 'diff') {
|
|
16
|
+
await handleDiff();
|
|
17
|
+
} else {
|
|
18
|
+
await handleOffset();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function handleDiff() {
|
|
23
|
+
const answers = await inquirer.prompt([
|
|
24
|
+
{
|
|
25
|
+
type: 'input',
|
|
26
|
+
name: 'start',
|
|
27
|
+
message: 'Enter Start Date (YYYY-MM-DD HH:mm:ss or timestamp):',
|
|
28
|
+
default: dayjs().format('YYYY-MM-DD HH:mm:ss')
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
type: 'input',
|
|
32
|
+
name: 'end',
|
|
33
|
+
message: 'Enter End Date (YYYY-MM-DD HH:mm:ss or timestamp):',
|
|
34
|
+
default: dayjs().format('YYYY-MM-DD HH:mm:ss')
|
|
35
|
+
}
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
const start = parseDate(answers.start);
|
|
39
|
+
const end = parseDate(answers.end);
|
|
40
|
+
|
|
41
|
+
if (!start.isValid() || !end.isValid()) {
|
|
42
|
+
console.error('Invalid date format.');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const diffMs = end.diff(start);
|
|
47
|
+
const dur = dayjs.duration(diffMs);
|
|
48
|
+
|
|
49
|
+
const result = [
|
|
50
|
+
`Start: ${start.format('YYYY-MM-DD HH:mm:ss')}`,
|
|
51
|
+
`End: ${end.format('YYYY-MM-DD HH:mm:ss')}`,
|
|
52
|
+
'---------------------------------',
|
|
53
|
+
`Total Days: ${dur.asDays().toFixed(2)}`,
|
|
54
|
+
`Total Hours: ${dur.asHours().toFixed(2)}`,
|
|
55
|
+
`Total Minutes: ${dur.asMinutes().toFixed(2)}`,
|
|
56
|
+
`Total Seconds: ${dur.asSeconds().toFixed(0)}`,
|
|
57
|
+
'---------------------------------',
|
|
58
|
+
`Human Readable: ${formatDuration(dur)}`
|
|
59
|
+
].join('\n');
|
|
60
|
+
|
|
61
|
+
console.log(`\n${result}\n`);
|
|
62
|
+
await copy(result);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function handleOffset() {
|
|
66
|
+
const { startInput } = await inquirer.prompt([
|
|
67
|
+
{
|
|
68
|
+
type: 'input',
|
|
69
|
+
name: 'startInput',
|
|
70
|
+
message: 'Enter Start Date:',
|
|
71
|
+
default: dayjs().format('YYYY-MM-DD HH:mm:ss')
|
|
72
|
+
}
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
const start = parseDate(startInput);
|
|
76
|
+
if (!start.isValid()) {
|
|
77
|
+
console.error('Invalid date format.');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const operator = await selectFromMenu('Select Operation', [
|
|
82
|
+
{ name: 'Add (+)', value: 'add' },
|
|
83
|
+
{ name: 'Subtract (-)', value: 'subtract' }
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
const unit = await selectFromMenu('Select Unit', [
|
|
87
|
+
{ name: 'Days', value: 'day' },
|
|
88
|
+
{ name: 'Hours', value: 'hour' },
|
|
89
|
+
{ name: 'Minutes', value: 'minute' },
|
|
90
|
+
{ name: 'Seconds', value: 'second' },
|
|
91
|
+
{ name: 'Weeks', value: 'week' },
|
|
92
|
+
{ name: 'Months', value: 'month' },
|
|
93
|
+
{ name: 'Years', value: 'year' }
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
const { amount } = await inquirer.prompt([
|
|
97
|
+
{
|
|
98
|
+
type: 'number',
|
|
99
|
+
name: 'amount',
|
|
100
|
+
message: `Enter Amount of ${unit}s (number):`,
|
|
101
|
+
validate: val => !isNaN(val) || 'Please enter a number'
|
|
102
|
+
}
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
let resultDate;
|
|
106
|
+
if (operator === 'add') {
|
|
107
|
+
resultDate = start.add(amount, unit);
|
|
108
|
+
} else {
|
|
109
|
+
resultDate = start.subtract(amount, unit);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const result = resultDate.format('YYYY-MM-DD HH:mm:ss');
|
|
113
|
+
console.log(`\nResult Date: ${result}\n`);
|
|
114
|
+
await copy(result);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function parseDate(input) {
|
|
118
|
+
if (!input) return dayjs();
|
|
119
|
+
if (/^\d+$/.test(input)) {
|
|
120
|
+
const ts = parseInt(input);
|
|
121
|
+
if (input.length === 10) return dayjs.unix(ts);
|
|
122
|
+
return dayjs(ts);
|
|
123
|
+
}
|
|
124
|
+
return dayjs(input);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function formatDuration(dur) {
|
|
128
|
+
const parts = [];
|
|
129
|
+
const years = Math.floor(dur.asYears());
|
|
130
|
+
if (years > 0) parts.push(`${years} years`);
|
|
131
|
+
|
|
132
|
+
// Calculate remaining duration after removing years is tricky with dayjs duration directly
|
|
133
|
+
// simplified approach:
|
|
134
|
+
|
|
135
|
+
const d = Math.abs(dur.asMilliseconds());
|
|
136
|
+
const seconds = Math.floor((d / 1000) % 60);
|
|
137
|
+
const minutes = Math.floor((d / (1000 * 60)) % 60);
|
|
138
|
+
const hours = Math.floor((d / (1000 * 60 * 60)) % 24);
|
|
139
|
+
const days = Math.floor(d / (1000 * 60 * 60 * 24));
|
|
140
|
+
|
|
141
|
+
// Note: This simple day calc doesn't account for variable month lengths perfectly if we wanted "X months Y days"
|
|
142
|
+
// But for "X days Y hours..." it's accurate enough for absolute diff.
|
|
143
|
+
|
|
144
|
+
let str = '';
|
|
145
|
+
if (days > 0) str += `${days} days `;
|
|
146
|
+
if (hours > 0) str += `${hours} hours `;
|
|
147
|
+
if (minutes > 0) str += `${minutes} mins `;
|
|
148
|
+
if (seconds > 0) str += `${seconds} secs`;
|
|
149
|
+
|
|
150
|
+
return str.trim() || '0 secs';
|
|
151
|
+
}
|
package/src/index.js
CHANGED
|
@@ -6,11 +6,13 @@ import { unicodeHandler } from './commands/unicode.js';
|
|
|
6
6
|
import { imgBase64Handler } from './commands/imgBase64.js';
|
|
7
7
|
import { imgConvertHandler } from './commands/imgConvert.js';
|
|
8
8
|
import { timeFormatHandler } from './commands/timeFormat.js';
|
|
9
|
+
import { timeCalcHandler } from './commands/timeCalc.js';
|
|
9
10
|
import { mockHandler } from './commands/mock.js';
|
|
10
11
|
import { uuidHandler } from './commands/uuid.js';
|
|
11
12
|
import { pinyinHandler } from './commands/pinyin.js';
|
|
12
13
|
import { colorHandler } from './commands/color.js';
|
|
13
14
|
import { variableFormatHandler } from './commands/variableFormat.js';
|
|
15
|
+
import { jsonFormatHandler } from './commands/jsonFormat.js';
|
|
14
16
|
import { hashingHandler } from './commands/hashing.js';
|
|
15
17
|
import { qrcodeHandler } from './commands/qrcode.js';
|
|
16
18
|
import { specialCharsHandler } from './commands/specialChars.js';
|
|
@@ -46,10 +48,12 @@ const features = [
|
|
|
46
48
|
{ name: 'Unicode Encode/Decode', value: 'unicode' },
|
|
47
49
|
{ name: 'HTML Entity Encode/Decode', value: 'htmlEntities' },
|
|
48
50
|
{ name: 'Variable Format Converter', value: 'variableFormat' },
|
|
51
|
+
{ name: 'JSON Format (Minify/Prettify)', value: 'jsonFormat' },
|
|
49
52
|
{ name: 'Chinese to Pinyin', value: 'pinyin' },
|
|
50
53
|
|
|
51
54
|
// Utils (Time, Color, UUID, Hash)
|
|
52
55
|
{ name: 'Time Format / Timestamp', value: 'timeFormat' },
|
|
56
|
+
{ name: 'Time Calculation (Diff/Offset)', value: 'timeCalc' },
|
|
53
57
|
{ name: 'Color Converter (Hex <-> RGB)', value: 'color' },
|
|
54
58
|
{ name: 'Get UUID', value: 'uuid' },
|
|
55
59
|
{ name: 'Hash Calculator (MD5/SHA/SM3)', value: 'hashing' },
|
|
@@ -154,6 +158,9 @@ async function handleAction(action) {
|
|
|
154
158
|
case 'timeFormat':
|
|
155
159
|
await timeFormatHandler();
|
|
156
160
|
break;
|
|
161
|
+
case 'timeCalc':
|
|
162
|
+
await timeCalcHandler();
|
|
163
|
+
break;
|
|
157
164
|
case 'mock':
|
|
158
165
|
await mockHandler();
|
|
159
166
|
break;
|
|
@@ -169,6 +176,9 @@ async function handleAction(action) {
|
|
|
169
176
|
case 'variableFormat':
|
|
170
177
|
await variableFormatHandler();
|
|
171
178
|
break;
|
|
179
|
+
case 'jsonFormat':
|
|
180
|
+
await jsonFormatHandler();
|
|
181
|
+
break;
|
|
172
182
|
case 'hashing':
|
|
173
183
|
await hashingHandler();
|
|
174
184
|
break;
|