@rtorcato/js-common 1.2.0 → 1.3.1

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/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [1.3.1](https://github.com/rtorcato/js-common/compare/v1.3.0...v1.3.1) (2025-10-24)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Correct import paths in CLI for proper TypeScript compilation ([24221d5](https://github.com/rtorcato/js-common/commit/24221d5622fa70dabe78166c7266c825686466ab))
7
+
8
+ # [1.3.0](https://github.com/rtorcato/js-common/compare/v1.2.0...v1.3.0) (2025-10-24)
9
+
10
+
11
+ ### Features
12
+
13
+ * Add modern interactive CLI with developer integration ([8ffb144](https://github.com/rtorcato/js-common/commit/8ffb144dfb9b1c13a6a40df7c2dee98623c8a936))
14
+
1
15
  # [1.2.0](https://github.com/rtorcato/js-common/compare/v1.1.0...v1.2.0) (2025-10-24)
2
16
 
3
17
 
@@ -0,0 +1,459 @@
1
+ #!/usr/bin/env node
2
+ import chalk from 'chalk';
3
+ import chalkAnimation from 'chalk-animation';
4
+ import { program } from 'commander';
5
+ import figlet from 'figlet';
6
+ import gradient from 'gradient-string';
7
+ import inquirer from 'inquirer';
8
+ import { readFileSync } from 'node:fs';
9
+ import { dirname, resolve } from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+ // Get current directory
12
+ const currentDir = dirname(fileURLToPath(import.meta.url));
13
+ // Get package version
14
+ const packageJson = JSON.parse(readFileSync(resolve(currentDir, '../../package.json'), 'utf-8'));
15
+ program
16
+ .name('js-common')
17
+ .description(chalk.cyan('✨ CLI utilities from @rtorcato/js-common'))
18
+ .version(packageJson.version);
19
+ // Function categories for list and interactive mode
20
+ const functionCategories = {
21
+ date: {
22
+ name: 'šŸ“… Date & Time',
23
+ functions: [
24
+ { name: 'today', description: "Get today's date (YYYY-MM-DD)" },
25
+ { name: 'now', description: 'Get current timestamp' },
26
+ { name: 'between', description: 'Calculate days between dates' },
27
+ ],
28
+ },
29
+ math: {
30
+ name: 'šŸ”¢ Mathematical',
31
+ functions: [
32
+ { name: 'sum', description: 'Calculate sum of numbers' },
33
+ { name: 'avg', description: 'Calculate average of numbers' },
34
+ { name: 'random', description: 'Generate random number' },
35
+ { name: 'round', description: 'Round to decimal places' },
36
+ { name: 'clamp', description: 'Clamp between min/max' },
37
+ ],
38
+ },
39
+ text: {
40
+ name: 'šŸ“ Text Formatting',
41
+ functions: [
42
+ { name: 'capitalize', description: 'Capitalize first letter' },
43
+ { name: 'title', description: 'Convert to title case' },
44
+ { name: 'pad', description: 'Pad with leading zeros' },
45
+ ],
46
+ },
47
+ file: {
48
+ name: 'šŸ“ File Operations',
49
+ functions: [
50
+ { name: 'exists', description: 'Check if file exists' },
51
+ { name: 'ext', description: 'Get file extension' },
52
+ ],
53
+ },
54
+ security: {
55
+ name: 'šŸ”’ Security',
56
+ functions: [
57
+ { name: 'password', description: 'Check password strength' },
58
+ { name: 'token', description: 'Generate secure token' },
59
+ ],
60
+ },
61
+ validate: {
62
+ name: 'āœ… Validation',
63
+ functions: [{ name: 'url', description: 'Validate URL format' }],
64
+ },
65
+ system: {
66
+ name: 'šŸ’» System Info',
67
+ functions: [
68
+ { name: 'pid', description: 'Get process ID' },
69
+ { name: 'uptime', description: 'Get process uptime' },
70
+ { name: 'ci', description: 'Check CI environment' },
71
+ { name: 'node-version', description: 'Get Node.js version' },
72
+ ],
73
+ },
74
+ };
75
+ // List all functions - add as a separate command
76
+ program
77
+ .command('list')
78
+ .alias('ls')
79
+ .description('šŸ“‹ List all available functions')
80
+ .action(() => {
81
+ console.log(chalk.cyan('\n✨ Available Functions:\n'));
82
+ Object.entries(functionCategories).forEach(([key, category]) => {
83
+ console.log(chalk.bold(category.name));
84
+ console.log(); // Add space after category header
85
+ category.functions.forEach((func) => {
86
+ console.log(chalk.gray(` js-common ${key} ${func.name}`) + chalk.dim(` - ${func.description}`));
87
+ });
88
+ console.log(); // Add space after function list
89
+ });
90
+ console.log(chalk.yellow('šŸ’” Use ') +
91
+ chalk.green('js-common add') +
92
+ chalk.yellow(' to integrate functions into your project'));
93
+ console.log(chalk.yellow('šŸ’” Use ') +
94
+ chalk.green('js-common interactive') +
95
+ chalk.yellow(' for interactive mode'));
96
+ console.log(chalk.yellow('šŸ’” Use ') +
97
+ chalk.green('js-common <command> --help') +
98
+ chalk.yellow(' for detailed help\n'));
99
+ });
100
+ // Also add as an option for compatibility
101
+ program.option('-l, --list', 'List all available functions');
102
+ // Interactive mode
103
+ program
104
+ .command('interactive')
105
+ .alias('i')
106
+ .description('šŸŽ® Interactive mode with guided prompts')
107
+ .action(async () => {
108
+ // Welcome animation
109
+ const title = figlet.textSync('JS Common', { font: 'Small' });
110
+ console.log(gradient.pastel.multiline(title));
111
+ const rainbow = chalkAnimation.rainbow('\n✨ Welcome to JS Common Interactive Mode! ✨\n');
112
+ await new Promise((resolve) => setTimeout(resolve, 1000));
113
+ rainbow.stop();
114
+ try {
115
+ // Main interactive loop
116
+ while (true) {
117
+ const { category } = await inquirer.prompt([
118
+ {
119
+ type: 'list',
120
+ name: 'category',
121
+ message: chalk.cyan('What would you like to do?'),
122
+ choices: Object.entries(functionCategories)
123
+ .reduce((acc, [key, cat], index) => {
124
+ acc.push({ name: cat.name, value: key });
125
+ // Add separator after each item except the last one
126
+ if (index < Object.entries(functionCategories).length - 1) {
127
+ acc.push(new inquirer.Separator(' '));
128
+ }
129
+ return acc;
130
+ }, [])
131
+ .concat([new inquirer.Separator(' '), { name: chalk.red('āŒ Exit'), value: 'exit' }]),
132
+ pageSize: 17, // Show more items to accommodate separators and exit option
133
+ loop: false, // Don't loop at the end
134
+ },
135
+ ]);
136
+ // Handle exit selection
137
+ if (category === 'exit') {
138
+ console.log(chalk.yellow('\nšŸ‘‹ Goodbye!\n'));
139
+ return;
140
+ }
141
+ const selectedCategory = functionCategories[category];
142
+ // Function selection loop
143
+ while (true) {
144
+ const { functionName } = await inquirer.prompt([
145
+ {
146
+ type: 'list',
147
+ name: 'functionName',
148
+ message: chalk.cyan(`Choose a ${selectedCategory.name} function:`),
149
+ choices: selectedCategory.functions
150
+ .reduce((acc, func, index) => {
151
+ acc.push({ name: `${func.name} - ${func.description}`, value: func.name });
152
+ // Add separator after each item except the last one
153
+ if (index < selectedCategory.functions.length - 1) {
154
+ acc.push(new inquirer.Separator(' '));
155
+ }
156
+ return acc;
157
+ }, [])
158
+ .concat([
159
+ new inquirer.Separator(' '),
160
+ { name: chalk.gray('ā¬…ļø Back to categories'), value: 'back' },
161
+ { name: chalk.red('āŒ Exit'), value: 'exit' },
162
+ ]),
163
+ pageSize: 12, // Show more items to accommodate back/exit options
164
+ loop: false, // Don't loop at the end
165
+ },
166
+ ]);
167
+ // Handle navigation options
168
+ if (functionName === 'exit') {
169
+ console.log(chalk.yellow('\nšŸ‘‹ Goodbye!\n'));
170
+ return;
171
+ }
172
+ if (functionName === 'back') {
173
+ break; // Break out of function selection loop, go back to categories
174
+ }
175
+ // Show the command
176
+ console.log(chalk.green(`\nšŸš€ You can run this command:`));
177
+ console.log(chalk.yellow(`js-common ${category} ${functionName} [arguments]`));
178
+ console.log(chalk.gray(`\nFor detailed usage, run: js-common ${category} ${functionName} --help\n`));
179
+ return; // Exit after showing the command
180
+ }
181
+ }
182
+ }
183
+ catch (error) {
184
+ if (error && typeof error === 'object' && 'isTtyError' in error) {
185
+ console.log(chalk.red('\nāŒ Interactive mode requires a TTY terminal'));
186
+ }
187
+ else {
188
+ console.log(chalk.red('\nāŒ Something went wrong in interactive mode'));
189
+ }
190
+ process.exit(1);
191
+ }
192
+ });
193
+ // Add command - help developers integrate functions into their projects
194
+ program
195
+ .command('add')
196
+ .alias('a')
197
+ .description('šŸ”§ Add utility functions to your project')
198
+ .action(async () => {
199
+ // Welcome message
200
+ console.log(gradient.pastel('\nšŸ”§ Function Integration Helper\n'));
201
+ try {
202
+ const { category } = await inquirer.prompt([
203
+ {
204
+ type: 'list',
205
+ name: 'category',
206
+ message: chalk.cyan('Which category of utilities do you need?'),
207
+ choices: Object.entries(functionCategories)
208
+ .reduce((acc, [key, cat], index) => {
209
+ acc.push({ name: cat.name, value: key });
210
+ // Add separator after each item except the last one
211
+ if (index < Object.entries(functionCategories).length - 1) {
212
+ acc.push(new inquirer.Separator(' '));
213
+ }
214
+ return acc;
215
+ }, [])
216
+ .concat([new inquirer.Separator(' '), { name: chalk.red('āŒ Exit'), value: 'exit' }]),
217
+ pageSize: 17, // Show more items to accommodate separators and exit option
218
+ loop: false, // Don't loop at the end
219
+ },
220
+ ]);
221
+ // Handle exit selection
222
+ if (category === 'exit') {
223
+ console.log(chalk.yellow('\nšŸ‘‹ Goodbye!\n'));
224
+ return;
225
+ }
226
+ const selectedCategory = functionCategories[category];
227
+ const { functions } = await inquirer.prompt([
228
+ {
229
+ type: 'checkbox',
230
+ name: 'functions',
231
+ message: chalk.cyan(`Select ${selectedCategory.name} functions to add:`),
232
+ choices: selectedCategory.functions.map((func) => ({
233
+ name: `${func.name} - ${func.description}`,
234
+ value: func.name,
235
+ checked: false,
236
+ })),
237
+ pageSize: 10, // Show more items at once
238
+ loop: false, // Don't loop at the end
239
+ },
240
+ ]);
241
+ if (functions.length === 0) {
242
+ console.log(chalk.yellow('\nšŸ“ No functions selected. Exiting...\n'));
243
+ return;
244
+ }
245
+ console.log(chalk.green('\nšŸ“¦ Import Statement:'));
246
+ console.log(chalk.white(`import { ${functions.join(', ')} } from '@rtorcato/js-common/${category}'`));
247
+ console.log(chalk.green('\nšŸ“ Usage Examples:'));
248
+ functions.forEach((func) => {
249
+ const funcInfo = selectedCategory.functions.find((f) => f.name === func);
250
+ if (funcInfo) {
251
+ console.log(chalk.gray(`\n// ${funcInfo.description}`));
252
+ generateUsageExample(category, func);
253
+ }
254
+ });
255
+ console.log(chalk.green('\nšŸ“š Documentation:'));
256
+ console.log(chalk.blue(`https://github.com/rtorcato/js-common#${category}-utilities`));
257
+ console.log(chalk.yellow('\nšŸ’” Tip: Run ') +
258
+ chalk.green(`js-common ${category} ${functions[0]} --help`) +
259
+ chalk.yellow(' for detailed CLI usage\n'));
260
+ }
261
+ catch (error) {
262
+ if (error && typeof error === 'object' && 'isTtyError' in error) {
263
+ console.log(chalk.red('\nāŒ Interactive mode requires a TTY terminal'));
264
+ }
265
+ else {
266
+ console.log(chalk.red('\nāŒ Something went wrong'));
267
+ }
268
+ process.exit(1);
269
+ }
270
+ });
271
+ // Helper function to generate usage examples
272
+ function generateUsageExample(category, functionName) {
273
+ const examples = {
274
+ date: {
275
+ today: `const today = today(); // "${new Date().toISOString().split('T')[0]}"`,
276
+ now: `const timestamp = now(); // ${Date.now()}`,
277
+ between: `const days = daysBetween('2023-01-01', '2023-12-31'); // 364`,
278
+ },
279
+ math: {
280
+ sum: `const total = sum([1, 2, 3, 4, 5]); // 15`,
281
+ avg: `const average = avg([10, 20, 30]); // 20`,
282
+ random: `const num = randomBetween(1, 100); // 42`,
283
+ round: `const rounded = roundTo(3.14159, 2); // 3.14`,
284
+ clamp: `const clamped = clamp(150, 0, 100); // 100`,
285
+ },
286
+ text: {
287
+ capitalize: `const text = capitalize('hello world'); // "Hello world"`,
288
+ title: `const title = toTitleCase('hello world'); // "Hello World"`,
289
+ pad: `const padded = padWithZeros(42, 5); // "00042"`,
290
+ },
291
+ file: {
292
+ exists: `const exists = await fileExists('package.json'); // true`,
293
+ ext: `const extension = getFileExtension('file.txt'); // "txt"`,
294
+ },
295
+ security: {
296
+ password: `const isStrong = checkPasswordStrength('MyPass123!'); // true`,
297
+ token: `const token = generateSecureToken(32); // "abc123..."`,
298
+ },
299
+ validate: {
300
+ url: `const isValid = isValidUrl('https://example.com'); // true`,
301
+ },
302
+ system: {
303
+ pid: `const processId = getProcessId(); // 12345`,
304
+ uptime: `const uptime = getProcessUptime(); // 123.45`,
305
+ ci: `const isCI = isCiEnvironment(); // false`,
306
+ 'node-version': `const version = getNodeVersion(); // "18.17.0"`,
307
+ },
308
+ };
309
+ const example = examples[category]?.[functionName];
310
+ if (example) {
311
+ console.log(chalk.white(example));
312
+ }
313
+ }
314
+ // Date commands
315
+ const dateCmd = program.command('date').description('šŸ“… Date and time utilities');
316
+ dateCmd
317
+ .command('today')
318
+ .description("Get today's date (YYYY-MM-DD)")
319
+ .action(() => {
320
+ console.log(new Date().toISOString().split('T')[0]);
321
+ });
322
+ dateCmd
323
+ .command('now')
324
+ .description('Get current timestamp')
325
+ .option('-i, --iso', 'ISO format')
326
+ .option('-t, --time', 'Time only')
327
+ .action((options) => {
328
+ if (options.iso) {
329
+ console.log(new Date().toISOString());
330
+ }
331
+ else if (options.time) {
332
+ console.log(new Date().toTimeString());
333
+ }
334
+ else {
335
+ console.log(Date.now());
336
+ }
337
+ });
338
+ dateCmd
339
+ .command('between')
340
+ .description('Calculate days between dates')
341
+ .argument('<date1>', 'First date (YYYY-MM-DD)')
342
+ .argument('<date2>', 'Second date (YYYY-MM-DD)')
343
+ .action((date1, date2) => {
344
+ const d1 = new Date(date1);
345
+ const d2 = new Date(date2);
346
+ const timeDiff = Math.abs(d2.getTime() - d1.getTime());
347
+ const daysDiff = Math.ceil(timeDiff / (1000 * 3600 * 24));
348
+ console.log(`${daysDiff} days`);
349
+ });
350
+ // Math commands
351
+ const mathCmd = program.command('math').description('šŸ”¢ Mathematical utilities');
352
+ mathCmd
353
+ .command('sum')
354
+ .description('Calculate sum of numbers')
355
+ .argument('<numbers...>', 'Numbers to sum')
356
+ .action((numbers) => {
357
+ const nums = numbers.map(Number).filter((n) => !isNaN(n));
358
+ console.log(nums.reduce((a, b) => a + b, 0));
359
+ });
360
+ mathCmd
361
+ .command('avg')
362
+ .description('Calculate average of numbers')
363
+ .argument('<numbers...>', 'Numbers to average')
364
+ .action((numbers) => {
365
+ const nums = numbers.map(Number).filter((n) => !isNaN(n));
366
+ console.log(nums.reduce((a, b) => a + b, 0) / nums.length);
367
+ });
368
+ mathCmd
369
+ .command('random')
370
+ .description('Generate random number')
371
+ .argument('<min>', 'Minimum value')
372
+ .argument('<max>', 'Maximum value')
373
+ .action((min, max) => {
374
+ const minNum = Number(min);
375
+ const maxNum = Number(max);
376
+ console.log(Math.floor(Math.random() * (maxNum - minNum + 1)) + minNum);
377
+ });
378
+ mathCmd
379
+ .command('round')
380
+ .description('Round to decimal places')
381
+ .argument('<number>', 'Number to round')
382
+ .argument('<decimals>', 'Decimal places')
383
+ .action((number, decimals) => {
384
+ const num = Number(number);
385
+ const dec = Number(decimals);
386
+ console.log(Number(num.toFixed(dec)));
387
+ });
388
+ mathCmd
389
+ .command('clamp')
390
+ .description('Clamp between min/max')
391
+ .argument('<number>', 'Number to clamp')
392
+ .argument('<min>', 'Minimum value')
393
+ .argument('<max>', 'Maximum value')
394
+ .action((number, min, max) => {
395
+ const num = Number(number);
396
+ const minVal = Number(min);
397
+ const maxVal = Number(max);
398
+ console.log(Math.min(Math.max(num, minVal), maxVal));
399
+ });
400
+ // Simple implementations for other commands
401
+ const textCmd = program.command('text').description('šŸ“ Text formatting utilities');
402
+ textCmd
403
+ .command('capitalize')
404
+ .description('Capitalize first letter')
405
+ .argument('<text>', 'Text to capitalize')
406
+ .action((text) => {
407
+ console.log(text.charAt(0).toUpperCase() + text.slice(1));
408
+ });
409
+ textCmd
410
+ .command('title')
411
+ .description('Convert to title case')
412
+ .argument('<text>', 'Text to convert')
413
+ .action((text) => {
414
+ console.log(text.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substring(1).toLowerCase()));
415
+ });
416
+ // System commands
417
+ const systemCmd = program.command('system').description('šŸ’» System information utilities');
418
+ systemCmd
419
+ .command('pid')
420
+ .description('Get process ID')
421
+ .action(() => {
422
+ console.log(process.pid);
423
+ });
424
+ systemCmd
425
+ .command('uptime')
426
+ .description('Get process uptime')
427
+ .action(() => {
428
+ console.log(`${Math.floor(process.uptime())} seconds`);
429
+ });
430
+ systemCmd
431
+ .command('node-version')
432
+ .description('Get Node.js version')
433
+ .action(() => {
434
+ console.log(process.version);
435
+ });
436
+ // Handle --list option before parsing
437
+ if (process.argv.includes('--list') || process.argv.includes('-l')) {
438
+ console.log(chalk.cyan('\n✨ Available Functions:\n'));
439
+ Object.entries(functionCategories).forEach(([key, category]) => {
440
+ console.log(chalk.bold(category.name));
441
+ console.log(); // Add space after category header
442
+ category.functions.forEach((func) => {
443
+ console.log(chalk.gray(` js-common ${key} ${func.name}`) + chalk.dim(` - ${func.description}`));
444
+ });
445
+ console.log(); // Add space after function list
446
+ });
447
+ console.log(chalk.yellow('šŸ’” Use ') +
448
+ chalk.green('js-common add') +
449
+ chalk.yellow(' to integrate functions into your project'));
450
+ console.log(chalk.yellow('šŸ’” Use ') +
451
+ chalk.green('js-common interactive') +
452
+ chalk.yellow(' for interactive mode'));
453
+ console.log(chalk.yellow('šŸ’” Use ') +
454
+ chalk.green('js-common <command> --help') +
455
+ chalk.yellow(' for detailed help\n'));
456
+ process.exit(0);
457
+ }
458
+ // Parse command line arguments
459
+ program.parse();
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@rtorcato/js-common",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "JavaScript and TypeScript common code",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",
8
8
  "types": "dist/index.d.ts",
9
9
  "bin": {
10
- "js-common": "dist/cli/index.cjs"
10
+ "js-common": "dist/cli/index.mjs"
11
11
  },
12
12
  "license": "MIT",
13
13
  "keywords": [
@@ -28,7 +28,7 @@
28
28
  "scripts": {
29
29
  "build-dev": "rimraf ./dist && cross-env NODE_ENV=development node build.mjs && tsc --emitDeclarationOnly --project tsconfig.build.json && npm run build-cli",
30
30
  "build-prod": "rimraf ./dist && cross-env NODE_ENV=production node build.mjs && tsc --emitDeclarationOnly --project tsconfig.build.json && npm run build-cli",
31
- "build-cli": "esbuild src/cli/index.ts --bundle --platform=node --format=cjs --outfile=dist/cli/index.cjs.tmp && echo '#!/usr/bin/env node' > dist/cli/index.cjs && cat dist/cli/index.cjs.tmp >> dist/cli/index.cjs && rm dist/cli/index.cjs.tmp && chmod +x dist/cli/index.cjs",
31
+ "build-cli": "tsc src/cli/cli.ts --outDir dist/cli --target ES2022 --module ES2022 --moduleResolution bundler --allowImportingTsExtensions false --esModuleInterop --skipLibCheck && mv dist/cli/cli.js dist/cli/temp.mjs && echo '#!/usr/bin/env node' > dist/cli/index.mjs && cat dist/cli/temp.mjs >> dist/cli/index.mjs && rm dist/cli/temp.mjs && chmod +x dist/cli/index.mjs",
32
32
  "prepublishOnly1": "npm run build-prod",
33
33
  "==================== Common ====================": "",
34
34
  "lint": "pnpm exec biome lint .",
@@ -245,6 +245,9 @@
245
245
  "@semantic-release/github": "^12.0.0",
246
246
  "@semantic-release/npm": "^13.1.1",
247
247
  "@semantic-release/release-notes-generator": "^14.1.0",
248
+ "@types/chalk-animation": "^1.6.3",
249
+ "@types/figlet": "^1.7.0",
250
+ "@types/inquirer": "^9.0.9",
248
251
  "@types/luxon": "^3.7.1",
249
252
  "@types/node": "^24.9.1",
250
253
  "cross-env": "^10.1.0",
@@ -258,10 +261,16 @@
258
261
  "vitest": "^4.0.3"
259
262
  },
260
263
  "dependencies": {
264
+ "chalk": "^5.6.2",
265
+ "chalk-animation": "^2.0.3",
261
266
  "commander": "^14.0.1",
262
267
  "date-fns": "^4.1.0",
263
268
  "date-fns-tz": "^3.2.0",
269
+ "figlet": "^1.9.3",
270
+ "gradient-string": "^3.0.0",
271
+ "inquirer": "^12.10.0",
264
272
  "luxon": "^3.7.2",
273
+ "nanospinner": "^1.2.2",
265
274
  "pino": "^10.1.0",
266
275
  "short-uuid": "^5.2.0",
267
276
  "uuid": "^13.0.0",