killportall 1.0.4 → 1.1.0
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 +36 -4
- package/bin/killportall.js +31 -10
- package/package.json +11 -8
- package/scripts/postinstall.js +163 -0
- package/src/logger.js +13 -5
- package/src/output.js +129 -0
package/README.md
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
A powerful cross-platform Node.js CLI tool for efficiently killing processes on specified ports with detailed logging and interactive mode.
|
|
4
4
|
|
|
5
5
|
[](https://badge.fury.io/js/killportall)
|
|
6
|
+
[](https://github.com/siri1410/killportall/actions/workflows/ci.yml)
|
|
6
7
|
[](https://opensource.org/licenses/MIT)
|
|
7
|
-
[](https://nodejs.org)
|
|
8
9
|
[](https://www.npmjs.com/package/killportall)
|
|
9
10
|
|
|
10
11
|
## Features
|
|
@@ -16,6 +17,7 @@ A powerful cross-platform Node.js CLI tool for efficiently killing processes on
|
|
|
16
17
|
- ⚙️ Configuration file support
|
|
17
18
|
- 🖥️ Cross-platform support (Windows, macOS, Linux)
|
|
18
19
|
- 🎨 JSON output format for scripting
|
|
20
|
+
- ♿ Accessibility support (NO_COLOR, screen reader friendly)
|
|
19
21
|
- 🚀 Fast and efficient port killing
|
|
20
22
|
- 🔍 Debug mode for troubleshooting
|
|
21
23
|
- 🛡️ Robust error handling
|
|
@@ -38,6 +40,8 @@ killportall -i
|
|
|
38
40
|
|
|
39
41
|
## Installation
|
|
40
42
|
|
|
43
|
+
**Requirements:** Node.js >= 18.0.0
|
|
44
|
+
|
|
41
45
|
### Global Installation (Recommended)
|
|
42
46
|
```bash
|
|
43
47
|
npm install -g killportall
|
|
@@ -96,8 +100,10 @@ killportall 3000 --timeout 2000
|
|
|
96
100
|
| `-t, --timeout <ms>` | Timeout between retries | 1000 |
|
|
97
101
|
| `-i, --interactive` | Run in interactive mode | false |
|
|
98
102
|
| `-j, --json` | Output results in JSON format | false |
|
|
103
|
+
| `--no-color` | Disable colored output | - |
|
|
104
|
+
| `--accessible` | Use text labels instead of symbols | - |
|
|
99
105
|
| `--config <key=value>` | Set configuration value | - |
|
|
100
|
-
| `-
|
|
106
|
+
| `-V, --version` | Display version number | - |
|
|
101
107
|
| `-h, --help` | Display help information | - |
|
|
102
108
|
|
|
103
109
|
### Configuration
|
|
@@ -123,6 +129,32 @@ DEBUG=killportall:* killportall 3000
|
|
|
123
129
|
DEBUG=killportall:cli,killportall:config killportall 3000
|
|
124
130
|
```
|
|
125
131
|
|
|
132
|
+
### Accessibility
|
|
133
|
+
|
|
134
|
+
killportall supports accessibility features for users with visual impairments or those using screen readers.
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
# Disable colored output
|
|
138
|
+
killportall 3000 --no-color
|
|
139
|
+
|
|
140
|
+
# Use text labels instead of symbols (screen reader friendly)
|
|
141
|
+
killportall 3000 --accessible
|
|
142
|
+
# Output: [OK] Port 3000: Process killed successfully
|
|
143
|
+
|
|
144
|
+
# Combine both for maximum accessibility
|
|
145
|
+
killportall 3000 --no-color --accessible
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Environment Variables:**
|
|
149
|
+
- `NO_COLOR` - Set to any value to disable colors (see [no-color.org](https://no-color.org/))
|
|
150
|
+
- `FORCE_COLOR` - Set to `1` to force colors, `0` to disable
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# Using environment variables
|
|
154
|
+
NO_COLOR=1 killportall 3000
|
|
155
|
+
FORCE_COLOR=0 killportall 3000
|
|
156
|
+
```
|
|
157
|
+
|
|
126
158
|
## API Usage
|
|
127
159
|
|
|
128
160
|
```javascript
|
|
@@ -198,8 +230,8 @@ npm publish --tag beta
|
|
|
198
230
|
|
|
199
231
|
4. Create release:
|
|
200
232
|
```bash
|
|
201
|
-
git tag v1.0
|
|
202
|
-
git push origin v1.0
|
|
233
|
+
git tag v1.1.0
|
|
234
|
+
git push origin v1.1.0
|
|
203
235
|
```
|
|
204
236
|
|
|
205
237
|
## Version Guidelines
|
package/bin/killportall.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
4
|
import debug from 'debug';
|
|
6
5
|
import { killPorts } from '../src/portKiller.js';
|
|
7
6
|
import { validatePorts } from '../src/utils.js';
|
|
@@ -10,6 +9,13 @@ import { fileURLToPath } from 'url';
|
|
|
10
9
|
import { dirname, join } from 'path';
|
|
11
10
|
import { readFileSync } from 'fs';
|
|
12
11
|
import { loadConfig, saveConfig } from '../src/config.js';
|
|
12
|
+
import {
|
|
13
|
+
setColorEnabled,
|
|
14
|
+
setAccessibleMode,
|
|
15
|
+
setQuietMode,
|
|
16
|
+
colorize,
|
|
17
|
+
getSymbol,
|
|
18
|
+
} from '../src/output.js';
|
|
13
19
|
|
|
14
20
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
21
|
const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json')));
|
|
@@ -26,14 +32,24 @@ program
|
|
|
26
32
|
.option('-t, --timeout <ms>', 'Timeout between retries in milliseconds', String(config.timeout))
|
|
27
33
|
.option('-i, --interactive', 'Run in interactive mode to select ports from a list', config.interactive)
|
|
28
34
|
.option('-j, --json', 'Output results in JSON format')
|
|
35
|
+
.option('--no-color', 'Disable colored output')
|
|
36
|
+
.option('--accessible', 'Use text labels instead of symbols (screen reader friendly)')
|
|
29
37
|
.option('--config <key=value>', 'Set a configuration value (retries, timeout, interactive, outputFormat)')
|
|
30
38
|
.action(async (ports, options) => {
|
|
31
39
|
try {
|
|
40
|
+
// Handle accessibility options
|
|
41
|
+
if (options.color === false) {
|
|
42
|
+
setColorEnabled(false);
|
|
43
|
+
}
|
|
44
|
+
if (options.accessible) {
|
|
45
|
+
setAccessibleMode(true);
|
|
46
|
+
}
|
|
47
|
+
|
|
32
48
|
// Handle config setting
|
|
33
49
|
if (options.config) {
|
|
34
50
|
const [key, value] = options.config.split('=');
|
|
35
51
|
if (!key || !value) {
|
|
36
|
-
console.error(
|
|
52
|
+
console.error(colorize(`${getSymbol('error')} Error: Invalid config format. Use key=value`, 'red'));
|
|
37
53
|
process.exit(1);
|
|
38
54
|
}
|
|
39
55
|
const newConfig = {};
|
|
@@ -49,17 +65,17 @@ program
|
|
|
49
65
|
break;
|
|
50
66
|
case 'outputFormat':
|
|
51
67
|
if (!['text', 'json'].includes(value)) {
|
|
52
|
-
console.error(
|
|
68
|
+
console.error(colorize(`${getSymbol('error')} Error: outputFormat must be either "text" or "json"`, 'red'));
|
|
53
69
|
process.exit(1);
|
|
54
70
|
}
|
|
55
71
|
newConfig.outputFormat = value;
|
|
56
72
|
break;
|
|
57
73
|
default:
|
|
58
|
-
console.error(
|
|
74
|
+
console.error(colorize(`${getSymbol('error')} Error: Unknown config key "${key}"`, 'red'));
|
|
59
75
|
process.exit(1);
|
|
60
76
|
}
|
|
61
77
|
if (saveConfig({ ...config, ...newConfig })) {
|
|
62
|
-
console.log(
|
|
78
|
+
console.log(colorize(`${getSymbol('success')} Configuration saved successfully`, 'green'));
|
|
63
79
|
process.exit(0);
|
|
64
80
|
} else {
|
|
65
81
|
process.exit(1);
|
|
@@ -67,8 +83,13 @@ program
|
|
|
67
83
|
}
|
|
68
84
|
|
|
69
85
|
debugLog('Starting port killing process with arguments:', { ports, options });
|
|
70
|
-
|
|
86
|
+
|
|
71
87
|
const outputFormat = options.json ? 'json' : config.outputFormat;
|
|
88
|
+
|
|
89
|
+
// Enable quiet mode for JSON output to suppress logger messages
|
|
90
|
+
if (outputFormat === 'json') {
|
|
91
|
+
setQuietMode(true);
|
|
92
|
+
}
|
|
72
93
|
let results;
|
|
73
94
|
|
|
74
95
|
if (options.interactive || config.interactive || ports.length === 0) {
|
|
@@ -79,7 +100,7 @@ program
|
|
|
79
100
|
} else {
|
|
80
101
|
const validatedPorts = validatePorts(ports);
|
|
81
102
|
if (!validatedPorts.length) {
|
|
82
|
-
console.error(
|
|
103
|
+
console.error(colorize(`${getSymbol('error')} Error: No valid ports specified`, 'red'));
|
|
83
104
|
process.exit(1);
|
|
84
105
|
}
|
|
85
106
|
|
|
@@ -94,15 +115,15 @@ program
|
|
|
94
115
|
} else {
|
|
95
116
|
results.forEach(result => {
|
|
96
117
|
if (result.success) {
|
|
97
|
-
console.log(
|
|
118
|
+
console.log(colorize(`${getSymbol('success')} Port ${result.port}: Process killed successfully`, 'green'));
|
|
98
119
|
} else {
|
|
99
|
-
console.error(
|
|
120
|
+
console.error(colorize(`${getSymbol('error')} Port ${result.port}: ${result.error}`, 'red'));
|
|
100
121
|
}
|
|
101
122
|
});
|
|
102
123
|
}
|
|
103
124
|
|
|
104
125
|
} catch (error) {
|
|
105
|
-
console.error(
|
|
126
|
+
console.error(colorize(`${getSymbol('error')} Error: ${error.message}`, 'red'));
|
|
106
127
|
debugLog('Error details:', error);
|
|
107
128
|
process.exit(1);
|
|
108
129
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "killportall",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A cross-platform Node.js CLI tool for efficiently killing processes on specified ports",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "bin/killportall.js",
|
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"bin/",
|
|
12
12
|
"src/",
|
|
13
|
+
"scripts/",
|
|
13
14
|
"LICENSE"
|
|
14
15
|
],
|
|
15
16
|
"engines": {
|
|
16
|
-
"node": ">=
|
|
17
|
+
"node": ">=18.0.0"
|
|
17
18
|
},
|
|
18
19
|
"directories": {
|
|
19
20
|
"test": "tests"
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --no-cache",
|
|
23
24
|
"start": "node bin/killportall.js",
|
|
24
25
|
"prepublishOnly": "npm test",
|
|
25
|
-
"postinstall": "chmod +x bin/killportall.js",
|
|
26
|
+
"postinstall": "chmod +x bin/killportall.js && node scripts/postinstall.js",
|
|
26
27
|
"preinstall": "node -e \"if(process.env.npm_config_global) { process.exit(0) }\"",
|
|
27
28
|
"setup": "chmod +x bin/killportall.js && npm link --force",
|
|
28
29
|
"clean": "npm unlink -g killportall || true",
|
|
@@ -47,11 +48,13 @@
|
|
|
47
48
|
},
|
|
48
49
|
"homepage": "https://github.com/siri1410/killportall#readme",
|
|
49
50
|
"dependencies": {
|
|
50
|
-
"chalk": "^5.
|
|
51
|
-
"commander": "^
|
|
52
|
-
"cross-spawn": "^7.0.
|
|
53
|
-
"debug": "^4.
|
|
54
|
-
"inquirer": "^12.
|
|
51
|
+
"chalk": "^5.4.1",
|
|
52
|
+
"commander": "^14.0.0",
|
|
53
|
+
"cross-spawn": "^7.0.6",
|
|
54
|
+
"debug": "^4.4.0",
|
|
55
|
+
"inquirer": "^12.3.2"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
55
58
|
"jest": "^29.7.0"
|
|
56
59
|
}
|
|
57
60
|
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createInterface } from 'readline';
|
|
4
|
+
import { existsSync, appendFileSync, readFileSync } from 'fs';
|
|
5
|
+
import { homedir } from 'os';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
|
|
8
|
+
const DEFAULT_ALIAS = 'kp';
|
|
9
|
+
|
|
10
|
+
// Check if colors should be disabled (NO_COLOR support: https://no-color.org/)
|
|
11
|
+
function shouldUseColor() {
|
|
12
|
+
if (process.env.FORCE_COLOR !== undefined) {
|
|
13
|
+
return process.env.FORCE_COLOR !== '0';
|
|
14
|
+
}
|
|
15
|
+
if (process.env.NO_COLOR !== undefined) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return process.stdout.isTTY;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const useColor = shouldUseColor();
|
|
22
|
+
|
|
23
|
+
// ANSI color codes with NO_COLOR support
|
|
24
|
+
const colors = {
|
|
25
|
+
green: (text) => useColor ? `\x1b[32m${text}\x1b[0m` : text,
|
|
26
|
+
yellow: (text) => useColor ? `\x1b[33m${text}\x1b[0m` : text,
|
|
27
|
+
cyan: (text) => useColor ? `\x1b[36m${text}\x1b[0m` : text,
|
|
28
|
+
red: (text) => useColor ? `\x1b[31m${text}\x1b[0m` : text,
|
|
29
|
+
bold: (text) => useColor ? `\x1b[1m${text}\x1b[0m` : text,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Detect user's shell and config file
|
|
33
|
+
function detectShell() {
|
|
34
|
+
const shell = process.env.SHELL || '';
|
|
35
|
+
const home = homedir();
|
|
36
|
+
|
|
37
|
+
if (shell.includes('zsh')) {
|
|
38
|
+
return { name: 'zsh', config: join(home, '.zshrc') };
|
|
39
|
+
} else if (shell.includes('bash')) {
|
|
40
|
+
// Check for .bash_profile (macOS) or .bashrc (Linux)
|
|
41
|
+
const bashProfile = join(home, '.bash_profile');
|
|
42
|
+
const bashrc = join(home, '.bashrc');
|
|
43
|
+
if (process.platform === 'darwin' && existsSync(bashProfile)) {
|
|
44
|
+
return { name: 'bash', config: bashProfile };
|
|
45
|
+
}
|
|
46
|
+
return { name: 'bash', config: bashrc };
|
|
47
|
+
} else if (shell.includes('fish')) {
|
|
48
|
+
return { name: 'fish', config: join(home, '.config/fish/config.fish') };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Default to bashrc
|
|
52
|
+
return { name: 'bash', config: join(home, '.bashrc') };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Check if alias already exists
|
|
56
|
+
function aliasExists(configPath, aliasName) {
|
|
57
|
+
if (!existsSync(configPath)) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
const content = readFileSync(configPath, 'utf8');
|
|
61
|
+
const aliasPattern = new RegExp(`alias\\s+${aliasName}\\s*=`, 'm');
|
|
62
|
+
return aliasPattern.test(content);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Add alias to shell config
|
|
66
|
+
function addAlias(configPath, aliasName, shell) {
|
|
67
|
+
let aliasLine;
|
|
68
|
+
|
|
69
|
+
if (shell === 'fish') {
|
|
70
|
+
aliasLine = `\n# killportall alias\nalias ${aliasName}='killportall'\n`;
|
|
71
|
+
} else {
|
|
72
|
+
aliasLine = `\n# killportall alias\nalias ${aliasName}='killportall'\n`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
appendFileSync(configPath, aliasLine);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Simple readline prompt
|
|
80
|
+
function prompt(question) {
|
|
81
|
+
return new Promise((resolve) => {
|
|
82
|
+
const rl = createInterface({
|
|
83
|
+
input: process.stdin,
|
|
84
|
+
output: process.stdout,
|
|
85
|
+
});
|
|
86
|
+
rl.question(question, (answer) => {
|
|
87
|
+
rl.close();
|
|
88
|
+
resolve(answer);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function main() {
|
|
94
|
+
// Skip in CI environments or non-TTY
|
|
95
|
+
if (process.env.CI || !process.stdin.isTTY) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Skip if running as part of npm install in a project (not global)
|
|
100
|
+
if (!process.env.npm_config_global) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.log('');
|
|
105
|
+
console.log(colors.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
106
|
+
console.log(colors.bold(' killportall installed successfully! 🎉'));
|
|
107
|
+
console.log(colors.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
108
|
+
console.log('');
|
|
109
|
+
|
|
110
|
+
const shell = detectShell();
|
|
111
|
+
|
|
112
|
+
console.log(` Detected shell: ${colors.green(shell.name)}`);
|
|
113
|
+
console.log(` Config file: ${colors.yellow(shell.config)}`);
|
|
114
|
+
console.log('');
|
|
115
|
+
|
|
116
|
+
const setupAlias = await prompt(` Would you like to set up a shell alias? (y/n) [y]: `);
|
|
117
|
+
|
|
118
|
+
if (setupAlias.toLowerCase() !== 'n') {
|
|
119
|
+
const customAlias = await prompt(` Enter alias name [${DEFAULT_ALIAS}]: `);
|
|
120
|
+
const aliasName = customAlias.trim() || DEFAULT_ALIAS;
|
|
121
|
+
|
|
122
|
+
if (aliasExists(shell.config, aliasName)) {
|
|
123
|
+
console.log('');
|
|
124
|
+
console.log(colors.yellow(` ⚠ Alias '${aliasName}' already exists in ${shell.config}`));
|
|
125
|
+
console.log('');
|
|
126
|
+
} else {
|
|
127
|
+
try {
|
|
128
|
+
addAlias(shell.config, aliasName, shell.name);
|
|
129
|
+
console.log('');
|
|
130
|
+
console.log(colors.green(` ✓ Alias '${aliasName}' added to ${shell.config}`));
|
|
131
|
+
console.log('');
|
|
132
|
+
console.log(colors.cyan(' To use the alias now, run:'));
|
|
133
|
+
console.log(` ${colors.bold(`source ${shell.config}`)}`);
|
|
134
|
+
console.log('');
|
|
135
|
+
console.log(colors.cyan(' Or simply open a new terminal window.'));
|
|
136
|
+
console.log('');
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.log('');
|
|
139
|
+
console.log(colors.red(` ✗ Failed to add alias: ${error.message}`));
|
|
140
|
+
console.log('');
|
|
141
|
+
console.log(colors.yellow(' You can manually add this to your shell config:'));
|
|
142
|
+
console.log(` ${colors.bold(`alias ${aliasName}='killportall'`)}`);
|
|
143
|
+
console.log('');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
console.log('');
|
|
148
|
+
console.log(colors.yellow(' Skipping alias setup.'));
|
|
149
|
+
console.log('');
|
|
150
|
+
console.log(colors.cyan(' You can manually add an alias later:'));
|
|
151
|
+
console.log(` ${colors.bold(`alias ${DEFAULT_ALIAS}='killportall'`)}`);
|
|
152
|
+
console.log('');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log(colors.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
156
|
+
console.log(` Usage: ${colors.bold('killportall 3000')} or ${colors.bold('killportall -i')}`);
|
|
157
|
+
console.log(colors.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
158
|
+
console.log('');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
main().catch(() => {
|
|
162
|
+
// Silently fail - don't block installation
|
|
163
|
+
});
|
package/src/logger.js
CHANGED
|
@@ -1,26 +1,34 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
1
|
import debug from 'debug';
|
|
2
|
+
import { formatMessage, isQuietMode } from './output.js';
|
|
3
3
|
|
|
4
4
|
const debugLog = debug('killportall:logger');
|
|
5
5
|
|
|
6
6
|
class Logger {
|
|
7
7
|
info(message) {
|
|
8
|
-
|
|
8
|
+
if (!isQuietMode()) {
|
|
9
|
+
console.log(formatMessage(message, 'info'));
|
|
10
|
+
}
|
|
9
11
|
debugLog('INFO:', message);
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
success(message) {
|
|
13
|
-
|
|
15
|
+
if (!isQuietMode()) {
|
|
16
|
+
console.log(formatMessage(message, 'success'));
|
|
17
|
+
}
|
|
14
18
|
debugLog('SUCCESS:', message);
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
warn(message) {
|
|
18
|
-
|
|
22
|
+
if (!isQuietMode()) {
|
|
23
|
+
console.error(formatMessage(message, 'warning'));
|
|
24
|
+
}
|
|
19
25
|
debugLog('WARN:', message);
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
error(message) {
|
|
23
|
-
|
|
29
|
+
if (!isQuietMode()) {
|
|
30
|
+
console.error(formatMessage(message, 'error'));
|
|
31
|
+
}
|
|
24
32
|
debugLog('ERROR:', message);
|
|
25
33
|
}
|
|
26
34
|
}
|
package/src/output.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Centralized output utility for accessibility and color handling.
|
|
5
|
+
* Supports NO_COLOR (https://no-color.org/) and FORCE_COLOR environment variables.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Determine color support
|
|
9
|
+
function shouldUseColor() {
|
|
10
|
+
// FORCE_COLOR takes precedence
|
|
11
|
+
if (process.env.FORCE_COLOR !== undefined) {
|
|
12
|
+
return process.env.FORCE_COLOR !== '0';
|
|
13
|
+
}
|
|
14
|
+
// NO_COLOR disables colors (https://no-color.org/)
|
|
15
|
+
if (process.env.NO_COLOR !== undefined) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
// Check if chalk has color support
|
|
19
|
+
return chalk.level > 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Configuration state
|
|
23
|
+
const config = {
|
|
24
|
+
useColor: shouldUseColor(),
|
|
25
|
+
useAccessibleSymbols: false,
|
|
26
|
+
quiet: false,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Set whether to use colors in output
|
|
31
|
+
* @param {boolean} enabled
|
|
32
|
+
*/
|
|
33
|
+
export function setColorEnabled(enabled) {
|
|
34
|
+
config.useColor = enabled;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Set whether to use accessible text labels instead of symbols
|
|
39
|
+
* @param {boolean} enabled
|
|
40
|
+
*/
|
|
41
|
+
export function setAccessibleMode(enabled) {
|
|
42
|
+
config.useAccessibleSymbols = enabled;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Set quiet mode (suppress non-JSON output for --json flag)
|
|
47
|
+
* @param {boolean} enabled
|
|
48
|
+
*/
|
|
49
|
+
export function setQuietMode(enabled) {
|
|
50
|
+
config.quiet = enabled;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Check if colors are currently enabled
|
|
55
|
+
* @returns {boolean}
|
|
56
|
+
*/
|
|
57
|
+
export function isColorEnabled() {
|
|
58
|
+
return config.useColor;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check if accessible mode is enabled
|
|
63
|
+
* @returns {boolean}
|
|
64
|
+
*/
|
|
65
|
+
export function isAccessibleMode() {
|
|
66
|
+
return config.useAccessibleSymbols;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check if quiet mode is enabled
|
|
71
|
+
* @returns {boolean}
|
|
72
|
+
*/
|
|
73
|
+
export function isQuietMode() {
|
|
74
|
+
return config.quiet;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Symbol definitions with accessible alternatives
|
|
78
|
+
const symbols = {
|
|
79
|
+
info: { symbol: '\u2139', accessible: '[INFO]' }, // ℹ
|
|
80
|
+
success: { symbol: '\u2713', accessible: '[OK]' }, // ✓
|
|
81
|
+
warning: { symbol: '\u26A0', accessible: '[WARN]' }, // ⚠
|
|
82
|
+
error: { symbol: '\u2717', accessible: '[ERROR]' }, // ✗
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get the appropriate symbol or text label
|
|
87
|
+
* @param {'info' | 'success' | 'warning' | 'error'} type
|
|
88
|
+
* @returns {string}
|
|
89
|
+
*/
|
|
90
|
+
export function getSymbol(type) {
|
|
91
|
+
const def = symbols[type];
|
|
92
|
+
if (!def) return '';
|
|
93
|
+
return config.useAccessibleSymbols ? def.accessible : def.symbol;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Apply color to text if colors are enabled
|
|
98
|
+
* @param {string} text
|
|
99
|
+
* @param {'blue' | 'green' | 'yellow' | 'red' | 'cyan' | 'bold'} color
|
|
100
|
+
* @returns {string}
|
|
101
|
+
*/
|
|
102
|
+
export function colorize(text, color) {
|
|
103
|
+
if (!config.useColor) {
|
|
104
|
+
return text;
|
|
105
|
+
}
|
|
106
|
+
const colorFn = chalk[color];
|
|
107
|
+
return colorFn ? colorFn(text) : text;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Format a message with symbol and color
|
|
112
|
+
* @param {string} message
|
|
113
|
+
* @param {'info' | 'success' | 'warning' | 'error'} type
|
|
114
|
+
* @returns {string}
|
|
115
|
+
*/
|
|
116
|
+
export function formatMessage(message, type) {
|
|
117
|
+
const symbol = getSymbol(type);
|
|
118
|
+
const colorMap = {
|
|
119
|
+
info: 'blue',
|
|
120
|
+
success: 'green',
|
|
121
|
+
warning: 'yellow',
|
|
122
|
+
error: 'red',
|
|
123
|
+
};
|
|
124
|
+
const coloredSymbol = colorize(symbol, colorMap[type]);
|
|
125
|
+
return `${coloredSymbol} ${message}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Re-export chalk for direct use when needed
|
|
129
|
+
export { chalk };
|