@lionad/port-key 0.1.6 → 0.3.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 +34 -5
- package/locales/cn.json +1 -0
- package/locales/en.json +1 -0
- package/package.json +7 -6
- package/src/cli.js +24 -0
- package/src/config.js +1 -0
- package/src/port-key.js +34 -24
package/README.md
CHANGED
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
<strong>PortKey:A Simple, Practical Port Naming Strategy</strong>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
|
+
<p align="center">
|
|
12
|
+
<!-- LANGUAGES=("cn" "es" "fr" "de" "ja" "ko" "ru" "ar" "pt" "it") -->
|
|
13
|
+
<a href="./docs/README.cn.md">中文</a> | <a href="./docs/README.es.md">Español</a> | <a href="./docs/README.fr.md">Français</a> | <a href="./docs/README.de.md">Deutsch</a> | <a href="./docs/README.ja.md">日本語</a> | <a href="./docs/README.ko.md">한국어</a> | <a href="./docs/README.ru.md">Русский</a> | <a href="./docs/README.ar.md">العربية</a> | <a href="./docs/README.pt.md">Português</a> | <a href="./docs/README.it.md">Italiano</a>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
11
16
|
## Brief
|
|
12
17
|
|
|
13
18
|
Generate ports with a letter-to-number keyboard mapping
|
|
@@ -25,7 +30,7 @@ For example, I have more than ten Nuxt apps on my machine. If they all default t
|
|
|
25
30
|
|
|
26
31
|
Instead of picking random numbers, map the **project name to numbers based on the keyboard**, so the port is *readable* and *memorable*.
|
|
27
32
|
|
|
28
|
-
As long as the result is within the valid port range (**
|
|
33
|
+
As long as the result is within the valid port range (**1024–65535**) and doesn’t hit reserved/system ports, you can just use it.
|
|
29
34
|
|
|
30
35
|
More specifically: using a standard QWERTY keyboard, map each letter to a single digit based on its **row/column position**.
|
|
31
36
|
|
|
@@ -53,23 +58,45 @@ If a project needs multiple ports (frontend, backend, database, etc.), pick **on
|
|
|
53
58
|
|
|
54
59
|
### Valid port range
|
|
55
60
|
|
|
56
|
-
- Ports must be within **
|
|
57
|
-
-
|
|
58
|
-
-
|
|
61
|
+
- Ports must be within **1024–65535** (System ports 0-1023 are blocked).
|
|
62
|
+
- **System Ports (0-1023)**: Assigned by IETF. Strictly blocked.
|
|
63
|
+
- **User Ports (1024-49151)**: Assigned by IANA. Use with caution as they might conflict with registered services.
|
|
64
|
+
- **Dynamic/Private Ports (49152-65535)**: Not assigned. Safest for private or dynamic use.
|
|
59
65
|
|
|
60
66
|
---
|
|
61
67
|
|
|
62
68
|
## How to use
|
|
63
69
|
|
|
70
|
+
Simple command:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
npx -y @lionad/port-key <your-project-name>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Or you want a stdio MCP server:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
npx -y @lionad/port-key-mcp
|
|
64
80
|
```
|
|
65
|
-
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"mcpServers": {
|
|
85
|
+
"port-key": {
|
|
86
|
+
"command": "npx",
|
|
87
|
+
"args": ["@lionad/port-key-mcp"]
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
66
91
|
```
|
|
67
92
|
|
|
93
|
+
|
|
68
94
|
### CLI options
|
|
69
95
|
|
|
70
96
|
- `-m, --map <object>`: custom mapping (JSON or JS-like object literal)
|
|
71
97
|
- `--lang <code>`: output language (currently only `en` and `cn`, default: `cn`)
|
|
72
98
|
- `-d, --digits <count>`: preferred digit count for port (4 or 5, default: 4)
|
|
99
|
+
- `--padding-zero <true|false>`: Pad short ports with zero (default: true). e.g. "air" -> 1840
|
|
73
100
|
- `-h, --help`: show help
|
|
74
101
|
|
|
75
102
|
Examples:
|
|
@@ -96,6 +123,8 @@ A full Example:
|
|
|
96
123
|
{
|
|
97
124
|
// Preferred digit count for port (4 or 5)
|
|
98
125
|
"preferDigitCount": 5,
|
|
126
|
+
// Pad short ports with zero (default: true)
|
|
127
|
+
"paddingZero": true,
|
|
99
128
|
// Custom letter-to-digit mapping
|
|
100
129
|
"blockedPorts": [3000, 3001, 3002, 6666],
|
|
101
130
|
// Port range limits (inclusive)
|
package/locales/cn.json
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"optMap": " -m, --map <对象> 自定义映射(JSON 或 JS 对象字面量)",
|
|
8
8
|
"optLang": " --lang <代码> 输出语言(en 或 cn)",
|
|
9
9
|
"optDigits": " -d, --digits <位数> 端口优先位数(4 或 5,默认:4)",
|
|
10
|
+
"optPadding": " --padding-zero <true|false> 是否为短端口号补零(默认:true)",
|
|
10
11
|
"optHelp": " -h, --help 显示帮助",
|
|
11
12
|
"examples": "示例:",
|
|
12
13
|
"ex1": " npx @lionad/port-key cfetch # -> 34353",
|
package/locales/en.json
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"optMap": " -m, --map <object> Custom mapping (JSON or JS-like object literal)",
|
|
8
8
|
"optLang": " --lang <code> Output language (en or cn)",
|
|
9
9
|
"optDigits": " -d, --digits <count> Preferred digit count for port (4 or 5, default: 4)",
|
|
10
|
+
"optPadding": " --padding-zero <true|false> Pad short ports with zero (default: true)",
|
|
10
11
|
"optHelp": " -h, --help Show help",
|
|
11
12
|
"examples": "Examples:",
|
|
12
13
|
"ex1": " npx @lionad/port-key cfetch # -> 34353",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lionad/port-key",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A simple, practical port naming strategy",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./src/port-key.d.ts",
|
|
@@ -30,10 +30,6 @@
|
|
|
30
30
|
"README.md",
|
|
31
31
|
"LICENSE"
|
|
32
32
|
],
|
|
33
|
-
"scripts": {
|
|
34
|
-
"test": "vitest run",
|
|
35
|
-
"test:watch": "vitest"
|
|
36
|
-
},
|
|
37
33
|
"author": "Lionad",
|
|
38
34
|
"license": "ISC",
|
|
39
35
|
"repository": {
|
|
@@ -53,5 +49,10 @@
|
|
|
53
49
|
"dependencies": {},
|
|
54
50
|
"publishConfig": {
|
|
55
51
|
"registry": "https://registry.npmjs.org/"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "echo 'skip build steps in packages/core'",
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"test:watch": "vitest"
|
|
56
57
|
}
|
|
57
|
-
}
|
|
58
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -18,6 +18,7 @@ function formatHelp(lang = 'cn') {
|
|
|
18
18
|
t.optMap,
|
|
19
19
|
t.optLang,
|
|
20
20
|
t.optDigits,
|
|
21
|
+
t.optPadding,
|
|
21
22
|
t.optHelp,
|
|
22
23
|
'',
|
|
23
24
|
t.examples,
|
|
@@ -35,6 +36,7 @@ function parseArgv(argv) {
|
|
|
35
36
|
let showHelp = false;
|
|
36
37
|
let lang;
|
|
37
38
|
let preferDigitCount;
|
|
39
|
+
let paddingZero;
|
|
38
40
|
const positionals = [];
|
|
39
41
|
|
|
40
42
|
let i = 0;
|
|
@@ -78,6 +80,25 @@ function parseArgv(argv) {
|
|
|
78
80
|
continue;
|
|
79
81
|
}
|
|
80
82
|
|
|
83
|
+
if (token === '--padding-zero') {
|
|
84
|
+
const value = args[i + 1];
|
|
85
|
+
if (value === 'false') {
|
|
86
|
+
paddingZero = false;
|
|
87
|
+
i += 2;
|
|
88
|
+
} else if (value === 'true') {
|
|
89
|
+
paddingZero = true;
|
|
90
|
+
i += 2;
|
|
91
|
+
} else if (!value || value.startsWith('-')) {
|
|
92
|
+
// Treated as boolean flag (true) if no value provided
|
|
93
|
+
paddingZero = true;
|
|
94
|
+
i += 1;
|
|
95
|
+
} else {
|
|
96
|
+
// Fallback for unexpected values
|
|
97
|
+
throw new Error('Invalid value for --padding-zero. Use true or false.');
|
|
98
|
+
}
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
81
102
|
positionals.push(token);
|
|
82
103
|
i += 1;
|
|
83
104
|
}
|
|
@@ -87,6 +108,7 @@ function parseArgv(argv) {
|
|
|
87
108
|
lang,
|
|
88
109
|
showHelp,
|
|
89
110
|
preferDigitCount,
|
|
111
|
+
paddingZero,
|
|
90
112
|
input: positionals.join(' '),
|
|
91
113
|
};
|
|
92
114
|
}
|
|
@@ -125,6 +147,7 @@ function runCli(argv, stdout = process.stdout, stderr = process.stderr, deps = {
|
|
|
125
147
|
map: parsed.map !== DEFAULT_MAP ? parsed.map : undefined,
|
|
126
148
|
lang: parsed.lang,
|
|
127
149
|
preferDigitCount: parsed.preferDigitCount,
|
|
150
|
+
paddingZero: parsed.paddingZero,
|
|
128
151
|
});
|
|
129
152
|
|
|
130
153
|
const lang = getLangOrDefault(effective.lang);
|
|
@@ -151,6 +174,7 @@ function runCli(argv, stdout = process.stdout, stderr = process.stderr, deps = {
|
|
|
151
174
|
maxPort: effective.maxPort,
|
|
152
175
|
preferredRanges: effective.preferredRanges,
|
|
153
176
|
preferDigitCount: effective.preferDigitCount,
|
|
177
|
+
paddingZero: effective.paddingZero,
|
|
154
178
|
});
|
|
155
179
|
|
|
156
180
|
if (result.port === null) {
|
package/src/config.js
CHANGED
|
@@ -54,6 +54,7 @@ function mergeConfig(base, override) {
|
|
|
54
54
|
maxPort: b.maxPort ?? a.maxPort,
|
|
55
55
|
preferredRanges: b.preferredRanges ?? a.preferredRanges,
|
|
56
56
|
preferDigitCount: b.preferDigitCount ?? a.preferDigitCount,
|
|
57
|
+
paddingZero: b.paddingZero ?? a.paddingZero,
|
|
57
58
|
lang: b.lang ?? a.lang,
|
|
58
59
|
};
|
|
59
60
|
}
|
package/src/port-key.js
CHANGED
|
@@ -14,30 +14,8 @@ const DEFAULT_MAP = Object.freeze({
|
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
const DEFAULT_BLOCKED_PORTS = Object.freeze(
|
|
17
|
+
// well-known application ports
|
|
17
18
|
new Set([
|
|
18
|
-
0,
|
|
19
|
-
20,
|
|
20
|
-
21,
|
|
21
|
-
22,
|
|
22
|
-
23,
|
|
23
|
-
25,
|
|
24
|
-
53,
|
|
25
|
-
67,
|
|
26
|
-
68,
|
|
27
|
-
80,
|
|
28
|
-
110,
|
|
29
|
-
123,
|
|
30
|
-
143,
|
|
31
|
-
161,
|
|
32
|
-
162,
|
|
33
|
-
389,
|
|
34
|
-
443,
|
|
35
|
-
445,
|
|
36
|
-
465,
|
|
37
|
-
587,
|
|
38
|
-
636,
|
|
39
|
-
993,
|
|
40
|
-
995,
|
|
41
19
|
3000,
|
|
42
20
|
3001,
|
|
43
21
|
5000,
|
|
@@ -105,6 +83,8 @@ function isValidPort(port) {
|
|
|
105
83
|
}
|
|
106
84
|
|
|
107
85
|
function isPortBlocked(port, blockedPorts) {
|
|
86
|
+
// System Ports (0-1023) are assigned by IETF and should not be used.
|
|
87
|
+
if (port < 1024) return true;
|
|
108
88
|
if (blockedPorts && typeof blockedPorts.has === 'function') {
|
|
109
89
|
return blockedPorts.has(port);
|
|
110
90
|
}
|
|
@@ -117,19 +97,45 @@ function isPortBlocked(port, blockedPorts) {
|
|
|
117
97
|
function pickPortFromDigits(digits, options = {}) {
|
|
118
98
|
const raw = String(digits || '').replace(/[^0-9]/g, '');
|
|
119
99
|
if (!raw) return { port: null, reason: 'No digits found in input' };
|
|
120
|
-
|
|
100
|
+
|
|
101
|
+
const paddingZero = options.paddingZero !== false; // Default true
|
|
102
|
+
|
|
103
|
+
// If not padding, enforce strict length check
|
|
104
|
+
// If padding, allow short length as it will be padded
|
|
105
|
+
if (!paddingZero && raw.length < 2) return { port: null, reason: 'Not enough digits to form a candidate' };
|
|
121
106
|
|
|
122
107
|
const minPort = Number.isFinite(options.minPort) ? options.minPort : 0;
|
|
123
108
|
const maxPort = Number.isFinite(options.maxPort) ? options.maxPort : 65535;
|
|
124
109
|
const blockedPorts = options.blockedPorts || DEFAULT_BLOCKED_PORTS;
|
|
125
110
|
const preferDigitCount = options.preferDigitCount || 4;
|
|
111
|
+
// paddingZero is already defined above
|
|
126
112
|
|
|
127
113
|
const candidates = [];
|
|
128
114
|
const normalized = raw.replace(/^0+/, '');
|
|
115
|
+
|
|
116
|
+
// Padding Logic for short inputs like "air" (184)
|
|
117
|
+
// If normalized length is small, we can pad it with zeros to match preferDigitCount or more
|
|
118
|
+
const paddedCandidates = [];
|
|
119
|
+
if (paddingZero && normalized.length > 0 && normalized.length < preferDigitCount) {
|
|
120
|
+
let current = normalized;
|
|
121
|
+
// Fix: start padding loop immediately
|
|
122
|
+
while (current.length <= 5) {
|
|
123
|
+
// Only push if length >= 2 (valid port min length logic) and >= preferDigitCount (if we want to respect preference)
|
|
124
|
+
// Actually, the original requirement says "pad ... to match preferDigitCount or more".
|
|
125
|
+
if (current.length >= preferDigitCount) {
|
|
126
|
+
paddedCandidates.push(current);
|
|
127
|
+
}
|
|
128
|
+
current += '0';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
129
132
|
if (preferDigitCount && normalized.length >= preferDigitCount) {
|
|
130
133
|
candidates.push(normalized.slice(0, preferDigitCount));
|
|
131
134
|
candidates.push(normalized.slice(normalized.length - preferDigitCount));
|
|
132
135
|
} else {
|
|
136
|
+
// If not padding, try smaller lengths (if >= 2)
|
|
137
|
+
// Note: if paddedCandidates is empty, we must rely on this.
|
|
138
|
+
// But if we have paddedCandidates, we can still add these as fallbacks.
|
|
133
139
|
for (let len = Math.min(normalized.length, preferDigitCount); len >= 2; len -= 1) {
|
|
134
140
|
candidates.push(normalized.slice(0, len));
|
|
135
141
|
}
|
|
@@ -137,6 +143,9 @@ function pickPortFromDigits(digits, options = {}) {
|
|
|
137
143
|
candidates.push(normalized.slice(normalized.length - len));
|
|
138
144
|
}
|
|
139
145
|
}
|
|
146
|
+
|
|
147
|
+
// Merge padded candidates
|
|
148
|
+
candidates.push(...paddedCandidates);
|
|
140
149
|
|
|
141
150
|
const unique = Array.from(new Set(candidates));
|
|
142
151
|
const rejectedCandidates = [];
|
|
@@ -219,6 +228,7 @@ export {
|
|
|
219
228
|
normalizeInput,
|
|
220
229
|
mapToDigits,
|
|
221
230
|
isValidPort,
|
|
231
|
+
isPortBlocked,
|
|
222
232
|
pickPortFromDigits,
|
|
223
233
|
mapToPort,
|
|
224
234
|
parseUserMap,
|