xw-devtool-cli 1.0.2 → 1.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xw-devtool-cli",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "type": "module",
5
5
  "description": "基于node的开发者助手cli",
6
6
  "main": "index.js",
@@ -51,7 +51,9 @@
51
51
  "inquirer": "^13.1.0",
52
52
  "lorem-ipsum": "^2.0.8",
53
53
  "pinyin": "^4.0.0",
54
+ "qrcode": "^1.5.4",
54
55
  "sharp": "^0.33.5",
56
+ "tinycolor2": "^1.6.0",
55
57
  "uuid": "^13.0.0"
56
58
  }
57
59
  }
@@ -0,0 +1,56 @@
1
+ import inquirer from 'inquirer';
2
+ import tinycolor from 'tinycolor2';
3
+ import { copy } from '../utils/clipboard.js';
4
+ import { selectFromMenu } from '../utils/menu.js';
5
+
6
+ export async function colorHandler() {
7
+ const { input } = await inquirer.prompt([
8
+ {
9
+ type: 'input',
10
+ name: 'input',
11
+ message: 'Enter color (Hex, RGB, HSL, or Name):',
12
+ validate: (input) => {
13
+ const color = tinycolor(input);
14
+ return color.isValid() || 'Invalid color format';
15
+ }
16
+ }
17
+ ]);
18
+
19
+ const color = tinycolor(input);
20
+
21
+ const results = [];
22
+ results.push(`Hex: ${color.toHexString().toUpperCase()}`);
23
+ results.push(`RGB: ${color.toRgbString()}`);
24
+ results.push(`HSL: ${color.toHslString()}`);
25
+ results.push(`HSV: ${color.toHsvString()}`);
26
+
27
+ // CMYK conversion (manual, as tinycolor doesn't support it directly)
28
+ const rgb = color.toRgb();
29
+ const r = rgb.r / 255;
30
+ const g = rgb.g / 255;
31
+ const b = rgb.b / 255;
32
+ let k = 1 - Math.max(r, g, b);
33
+ let c = (1 - r - k) / (1 - k) || 0;
34
+ let m = (1 - g - k) / (1 - k) || 0;
35
+ let y = (1 - b - k) / (1 - k) || 0;
36
+
37
+ // Round to 2 decimal places
38
+ const toPercent = (n) => Math.round(n * 100);
39
+ const cmyk = `cmyk(${toPercent(c)}%, ${toPercent(m)}%, ${toPercent(y)}%, ${toPercent(k)}%)`;
40
+ results.push(`CMYK: ${cmyk}`);
41
+
42
+ console.log('\n=== Conversion Results ===');
43
+ results.forEach(res => console.log(res));
44
+ console.log('==========================\n');
45
+
46
+ // Allow user to copy one of the formats
47
+ const copyChoice = await selectFromMenu('Select format to copy', [
48
+ { name: 'Hex', value: color.toHexString().toUpperCase() },
49
+ { name: 'RGB', value: color.toRgbString() },
50
+ { name: 'HSL', value: color.toHslString() },
51
+ { name: 'HSV', value: color.toHsvString() },
52
+ { name: 'CMYK', value: cmyk }
53
+ ]);
54
+
55
+ await copy(copyChoice);
56
+ }
@@ -0,0 +1,58 @@
1
+ import inquirer from 'inquirer';
2
+ import crypto from 'node:crypto';
3
+ import { copy, read } from '../utils/clipboard.js';
4
+ import { selectFromMenu } from '../utils/menu.js';
5
+
6
+ export async function hashingHandler() {
7
+ const { input } = await inquirer.prompt([
8
+ {
9
+ type: 'input',
10
+ name: 'input',
11
+ message: 'Enter text to hash (Press Enter to paste from clipboard):',
12
+ }
13
+ ]);
14
+
15
+ let textToHash = input;
16
+
17
+ if (!textToHash || textToHash.length === 0) {
18
+ textToHash = await read();
19
+ if (!textToHash || textToHash.length === 0) {
20
+ console.log('Clipboard is empty or could not be read.');
21
+ return;
22
+ }
23
+ console.log(`\nUsing clipboard content: "${textToHash}"`);
24
+ }
25
+
26
+ const algorithms = [
27
+ { name: 'MD5', algo: 'md5' },
28
+ { name: 'SHA1', algo: 'sha1' },
29
+ { name: 'SHA256', algo: 'sha256' },
30
+ { name: 'SHA512', algo: 'sha512' },
31
+ { name: 'SM3', algo: 'sm3' }
32
+ ];
33
+
34
+ const results = [];
35
+
36
+ console.log('\n=== Hash Results ===');
37
+ for (const { name, algo } of algorithms) {
38
+ try {
39
+ const hash = crypto.createHash(algo).update(textToHash).digest('hex');
40
+ results.push({ name, value: hash });
41
+ console.log(`${name.padEnd(8)}: ${hash}`);
42
+ } catch (e) {
43
+ console.log(`${name.padEnd(8)}: Not supported in this environment`);
44
+ }
45
+ }
46
+ console.log('====================\n');
47
+
48
+ if (results.length === 0) return;
49
+
50
+ const copyChoice = await selectFromMenu('Select hash to copy',
51
+ results.map(r => ({
52
+ name: `${r.name} (${r.value.substring(0, 10)}...)`,
53
+ value: r.value
54
+ }))
55
+ );
56
+
57
+ await copy(copyChoice);
58
+ }
@@ -0,0 +1,61 @@
1
+ import inquirer from 'inquirer';
2
+ import QRCode from 'qrcode';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import dayjs from 'dayjs';
6
+ import { read } from '../utils/clipboard.js';
7
+ import { selectFromMenu } from '../utils/menu.js';
8
+
9
+ export async function qrcodeHandler() {
10
+ const { input } = await inquirer.prompt([
11
+ {
12
+ type: 'input',
13
+ name: 'input',
14
+ message: 'Enter text to generate QR code (Press Enter to paste from clipboard):',
15
+ }
16
+ ]);
17
+
18
+ let textToEncode = input;
19
+
20
+ if (!textToEncode || textToEncode.length === 0) {
21
+ textToEncode = await read();
22
+ if (!textToEncode || textToEncode.length === 0) {
23
+ console.log('Clipboard is empty or could not be read.');
24
+ return;
25
+ }
26
+ console.log(`\nUsing clipboard content: "${textToEncode}"`);
27
+ }
28
+
29
+ try {
30
+ // Display in terminal
31
+ const string = await QRCode.toString(textToEncode, { type: 'terminal', small: true });
32
+ console.log('\n=== QR Code ===\n');
33
+ console.log(string);
34
+ console.log('===============\n');
35
+
36
+ // Ask if user wants to save to file
37
+ const action = await selectFromMenu('What would you like to do next?', [
38
+ { name: 'Back to menu', value: 'back' },
39
+ { name: 'Save as PNG image', value: 'save' }
40
+ ]);
41
+
42
+ if (action === 'save') {
43
+ const defaultFilename = `qrcode_${dayjs().format('YYYYMMDD_HHmmss')}.png`;
44
+ const { filename } = await inquirer.prompt([
45
+ {
46
+ type: 'input',
47
+ name: 'filename',
48
+ message: `Enter filename (default: ${defaultFilename}):`,
49
+ default: defaultFilename
50
+ }
51
+ ]);
52
+
53
+ const outputPath = path.resolve(process.cwd(), filename);
54
+ await QRCode.toFile(outputPath, textToEncode);
55
+ console.log(`\nQR Code saved to: ${outputPath}\n`);
56
+ }
57
+
58
+ } catch (err) {
59
+ console.error('Error generating QR code:', err.message);
60
+ }
61
+ }
@@ -1,6 +1,10 @@
1
1
  import inquirer from 'inquirer';
2
2
  import dayjs from 'dayjs';
3
+ import 'dayjs/locale/zh-cn.js';
3
4
  import { copy } from '../utils/clipboard.js';
5
+ import { selectFromMenu } from '../utils/menu.js';
6
+
7
+ dayjs.locale('zh-cn');
4
8
 
5
9
  export async function timeFormatHandler() {
6
10
  const { input } = await inquirer.prompt([
@@ -35,7 +39,26 @@ export async function timeFormatHandler() {
35
39
  return;
36
40
  }
37
41
 
38
- const formatted = date.format('YYYY-MM-DD HH:mm:ss');
42
+ const formatPattern = await selectFromMenu('Select Output Format', [
43
+ { name: 'YYYY-MM-DD HH:mm:ss', value: 'YYYY-MM-DD HH:mm:ss' },
44
+ { name: 'YYYY-MM-DD HH:mm', value: 'YYYY-MM-DD HH:mm' },
45
+ { name: 'YYYY-MM-DD', value: 'YYYY-MM-DD' },
46
+ { name: 'HH:mm:ss', value: 'HH:mm:ss' },
47
+ { name: 'YYYY-MM-DD HH:mm:ss dddd', value: 'YYYY-MM-DD HH:mm:ss dddd' },
48
+ { name: 'dddd', value: 'dddd' },
49
+ { name: 'Timestamp (ms)', value: 'timestamp-ms' },
50
+ { name: 'Timestamp (s)', value: 'timestamp-s' }
51
+ ]);
52
+
53
+ let formatted;
54
+ if (formatPattern === 'timestamp-ms') {
55
+ formatted = String(date.valueOf());
56
+ } else if (formatPattern === 'timestamp-s') {
57
+ formatted = String(date.unix());
58
+ } else {
59
+ formatted = date.format(formatPattern);
60
+ }
61
+
39
62
  console.log(`\nFormatted: ${formatted}\n`);
40
63
  await copy(formatted);
41
64
  }
@@ -0,0 +1,94 @@
1
+ import inquirer from 'inquirer';
2
+ import { copy, read } from '../utils/clipboard.js';
3
+ import { selectFromMenu } from '../utils/menu.js';
4
+
5
+ export async function variableFormatHandler() {
6
+ const { input } = await inquirer.prompt([
7
+ {
8
+ type: 'input',
9
+ name: 'input',
10
+ message: 'Enter variable name to convert (Press Enter to paste from clipboard):',
11
+ }
12
+ ]);
13
+
14
+ let textToConvert = input;
15
+
16
+ if (!textToConvert || textToConvert.trim().length === 0) {
17
+ textToConvert = await read();
18
+ if (!textToConvert || textToConvert.trim().length === 0) {
19
+ console.log('Clipboard is empty or could not be read.');
20
+ return;
21
+ }
22
+ console.log(`\nUsing clipboard content: "${textToConvert}"`);
23
+ }
24
+
25
+ const words = splitIntoWords(textToConvert);
26
+
27
+ if (words.length === 0) {
28
+ console.log('Could not parse any words from input.');
29
+ return;
30
+ }
31
+
32
+ const results = {
33
+ 'camelCase': toCamelCase(words),
34
+ 'PascalCase': toPascalCase(words),
35
+ 'snake_case': toSnakeCase(words),
36
+ 'kebab-case': toKebabCase(words),
37
+ 'CONSTANT_CASE': toConstantCase(words)
38
+ };
39
+
40
+ console.log('\n=== Conversion Results ===');
41
+ Object.entries(results).forEach(([key, value]) => {
42
+ console.log(`${key.padEnd(15)}: ${value}`);
43
+ });
44
+ console.log('==========================\n');
45
+
46
+ const copyChoice = await selectFromMenu('Select format to copy',
47
+ Object.entries(results).map(([key, value]) => ({
48
+ name: `${key} (${value})`,
49
+ value: value
50
+ }))
51
+ );
52
+
53
+ await copy(copyChoice);
54
+ }
55
+
56
+ function splitIntoWords(str) {
57
+ // 1. Handle CamelCase/camelCase by inserting space before uppercase letters that follow lowercase letters
58
+ let temp = str.replace(/([a-z])([A-Z])/g, '$1 $2');
59
+
60
+ // 2. Handle consecutive uppercase followed by lowercase (e.g. JSONParser -> JSON Parser)
61
+ temp = temp.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2');
62
+
63
+ // 3. Replace non-alphanumeric characters with spaces
64
+ temp = temp.replace(/[^a-zA-Z0-9]+/g, ' ');
65
+
66
+ // 4. Split by whitespace and filter empty strings
67
+ return temp
68
+ .trim()
69
+ .split(/\s+/)
70
+ .filter(w => w.length > 0);
71
+ }
72
+
73
+ function toCamelCase(words) {
74
+ return words.map((w, i) => {
75
+ if (i === 0) return w.toLowerCase();
76
+ return w.charAt(0).toUpperCase() + w.slice(1).toLowerCase();
77
+ }).join('');
78
+ }
79
+
80
+ function toPascalCase(words) {
81
+ return words.map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
82
+ }
83
+
84
+ function toSnakeCase(words) {
85
+ return words.map(w => w.toLowerCase()).join('_');
86
+ }
87
+
88
+ function toKebabCase(words) {
89
+ return words.map(w => w.toLowerCase()).join('-');
90
+ }
91
+
92
+ function toConstantCase(words) {
93
+ return words.map(w => w.toUpperCase()).join('_');
94
+ }
package/src/index.js CHANGED
@@ -5,10 +5,13 @@ import { base64Handler } from './commands/base64.js';
5
5
  import { imgBase64Handler } from './commands/imgBase64.js';
6
6
  import { imgConvertHandler } from './commands/imgConvert.js';
7
7
  import { timeFormatHandler } from './commands/timeFormat.js';
8
- import { timestampHandler } from './commands/timestamp.js';
9
8
  import { mockHandler } from './commands/mock.js';
10
9
  import { uuidHandler } from './commands/uuid.js';
11
10
  import { pinyinHandler } from './commands/pinyin.js';
11
+ import { colorHandler } from './commands/color.js';
12
+ import { variableFormatHandler } from './commands/variableFormat.js';
13
+ import { hashingHandler } from './commands/hashing.js';
14
+ import { qrcodeHandler } from './commands/qrcode.js';
12
15
 
13
16
  process.on('SIGINT', () => {
14
17
  console.log('\nBye!');
@@ -30,11 +33,14 @@ const features = [
30
33
  { name: 'String Encode/Decode (Base64)', value: 'base64' },
31
34
  { name: 'Image <-> Base64', value: 'imgBase64' },
32
35
  { name: 'Image Format Convert', value: 'imgConvert' },
33
- { name: 'Time Format', value: 'timeFormat' },
34
- { name: 'Get Current Timestamp', value: 'timestamp' },
36
+ { name: 'Time Format / Timestamp', value: 'timeFormat' },
35
37
  { name: 'Mock Text', value: 'mock' },
36
38
  { name: 'Get UUID', value: 'uuid' },
37
- { name: 'Chinese to Pinyin', value: 'pinyin' }
39
+ { name: 'Chinese to Pinyin', value: 'pinyin' },
40
+ { name: 'Color Converter (Hex <-> RGB)', value: 'color' },
41
+ { name: 'Variable Format Converter', value: 'variableFormat' },
42
+ { name: 'Hash Calculator (MD5/SHA/SM3)', value: 'hashing' },
43
+ { name: 'QR Code Generator', value: 'qrcode' }
38
44
  ];
39
45
 
40
46
  async function main() {
@@ -48,12 +54,29 @@ async function main() {
48
54
  program.parse(process.argv);
49
55
  }
50
56
 
57
+ function getFeatureKey(index) {
58
+ if (index < 9) {
59
+ return String(index + 1);
60
+ }
61
+ return String.fromCharCode('a'.charCodeAt(0) + (index - 9));
62
+ }
63
+
64
+ function getFeatureIndex(key) {
65
+ if (/^[1-9]$/.test(key)) {
66
+ return parseInt(key) - 1;
67
+ }
68
+ if (/^[a-z]$/.test(key.toLowerCase())) {
69
+ return key.toLowerCase().charCodeAt(0) - 'a'.charCodeAt(0) + 9;
70
+ }
71
+ return -1;
72
+ }
73
+
51
74
  async function showMenu() {
52
75
  console.log('\n=================================');
53
76
  console.log(' xw-devtool-cli Menu');
54
77
  console.log('=================================');
55
78
  features.forEach((feature, index) => {
56
- console.log(`${index + 1}. ${feature.name}`);
79
+ console.log(`${getFeatureKey(index)}. ${feature.name}`);
57
80
  });
58
81
  console.log('0. Exit');
59
82
  console.log('=================================\n');
@@ -62,25 +85,26 @@ async function showMenu() {
62
85
  {
63
86
  type: 'input',
64
87
  name: 'choice',
65
- message: 'Please enter the feature number (0-8):',
88
+ message: `Please enter the feature key (0-9, a-z):`,
66
89
  validate: (input) => {
67
- const num = parseInt(input);
68
- if (isNaN(num) || num < 0 || num > features.length) {
69
- return `Please enter a number between 0 and ${features.length}`;
90
+ if (input === '0') return true;
91
+
92
+ const index = getFeatureIndex(input);
93
+ if (index >= 0 && index < features.length) {
94
+ return true;
70
95
  }
71
- return true;
96
+ return 'Invalid selection. Please enter a valid menu key.';
72
97
  }
73
98
  }
74
99
  ]);
75
100
 
76
- const index = parseInt(choice);
77
-
78
- if (index === 0) {
101
+ if (choice === '0') {
79
102
  console.log('Bye!');
80
103
  process.exit(0);
81
104
  }
82
105
 
83
- const selectedFeature = features[index - 1];
106
+ const index = getFeatureIndex(choice);
107
+ const selectedFeature = features[index];
84
108
 
85
109
  try {
86
110
  await handleAction(selectedFeature.value);
@@ -110,9 +134,6 @@ async function handleAction(action) {
110
134
  case 'timeFormat':
111
135
  await timeFormatHandler();
112
136
  break;
113
- case 'timestamp':
114
- await timestampHandler();
115
- break;
116
137
  case 'mock':
117
138
  await mockHandler();
118
139
  break;
@@ -122,6 +143,18 @@ async function handleAction(action) {
122
143
  case 'pinyin':
123
144
  await pinyinHandler();
124
145
  break;
146
+ case 'color':
147
+ await colorHandler();
148
+ break;
149
+ case 'variableFormat':
150
+ await variableFormatHandler();
151
+ break;
152
+ case 'hashing':
153
+ await hashingHandler();
154
+ break;
155
+ case 'qrcode':
156
+ await qrcodeHandler();
157
+ break;
125
158
  default:
126
159
  console.log('Feature not implemented yet.');
127
160
  }
@@ -8,3 +8,12 @@ export async function copy(text) {
8
8
  console.error('Failed to copy to clipboard (might not be supported in this environment):', e.message);
9
9
  }
10
10
  }
11
+
12
+ export async function read() {
13
+ try {
14
+ return await clipboardy.read();
15
+ } catch (e) {
16
+ console.error('Failed to read from clipboard:', e.message);
17
+ return '';
18
+ }
19
+ }
@@ -1,7 +0,0 @@
1
- import { copy } from '../utils/clipboard.js';
2
-
3
- export async function timestampHandler() {
4
- const now = Date.now();
5
- console.log(`\nCurrent Timestamp: ${now}\n`);
6
- await copy(String(now));
7
- }