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 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
  [![npm version](https://badge.fury.io/js/killportall.svg)](https://badge.fury.io/js/killportall)
6
+ [![CI](https://github.com/siri1410/killportall/actions/workflows/ci.yml/badge.svg)](https://github.com/siri1410/killportall/actions/workflows/ci.yml)
6
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
- [![Node.js Version](https://img.shields.io/node/v/killportall.svg)](https://nodejs.org)
8
+ [![Node.js Version](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen.svg)](https://nodejs.org)
8
9
  [![Downloads](https://img.shields.io/npm/dm/killportall.svg)](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
- | `-v, --version` | Display version number | - |
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.3
202
- git push origin v1.0.3
233
+ git tag v1.1.0
234
+ git push origin v1.1.0
203
235
  ```
204
236
 
205
237
  ## Version Guidelines
@@ -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(chalk.red('Error: Invalid config format. Use key=value'));
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(chalk.red('Error: outputFormat must be either "text" or "json"'));
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(chalk.red(`Error: Unknown config key "${key}"`));
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(chalk.green('Configuration saved successfully'));
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(chalk.red('Error: No valid ports specified'));
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(chalk.green(`✓ Port ${result.port}: Process killed successfully`));
118
+ console.log(colorize(`${getSymbol('success')} Port ${result.port}: Process killed successfully`, 'green'));
98
119
  } else {
99
- console.error(chalk.red(`✗ Port ${result.port}: ${result.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(chalk.red('Error:', error.message));
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.4",
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": ">=14.0.0"
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.3.0",
51
- "commander": "^12.1.0",
52
- "cross-spawn": "^7.0.3",
53
- "debug": "^4.3.7",
54
- "inquirer": "^12.0.1",
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
- console.log(chalk.blue('ℹ'), message);
8
+ if (!isQuietMode()) {
9
+ console.log(formatMessage(message, 'info'));
10
+ }
9
11
  debugLog('INFO:', message);
10
12
  }
11
13
 
12
14
  success(message) {
13
- console.log(chalk.green('✓'), message);
15
+ if (!isQuietMode()) {
16
+ console.log(formatMessage(message, 'success'));
17
+ }
14
18
  debugLog('SUCCESS:', message);
15
19
  }
16
20
 
17
21
  warn(message) {
18
- console.log(chalk.yellow('⚠'), message);
22
+ if (!isQuietMode()) {
23
+ console.error(formatMessage(message, 'warning'));
24
+ }
19
25
  debugLog('WARN:', message);
20
26
  }
21
27
 
22
28
  error(message) {
23
- console.error(chalk.red('✗'), message);
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 };