@lionad/port-key 0.4.1 → 0.6.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 +4 -4
- package/locales/cn.json +2 -2
- package/locales/en.json +2 -2
- package/package.json +1 -1
- package/src/cli.js +0 -1
- package/src/config.js +4 -3
- package/src/i18n.js +4 -1
- package/src/port-key.d.ts +2 -0
- package/src/port-key.js +35 -40
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# PortKey
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<img width="200" src="/public/logo.png" />
|
|
4
|
+
<img width="200" src="https://raw.githubusercontent.com/Lionad-Morotar/port-key/main/public/logo.png" />
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
@@ -96,7 +96,7 @@ npx -y @lionad/port-key-mcp
|
|
|
96
96
|
- `-m, --map <object>`: custom mapping (JSON or JS-like object literal)
|
|
97
97
|
- `--lang <code>`: output language (currently only `en` and `cn`, default: `cn`)
|
|
98
98
|
- `-d, --digits <count>`: preferred digit count for port (4 or 5, default: 4)
|
|
99
|
-
- `--padding-zero <true|false>`: Pad
|
|
99
|
+
- `--padding-zero <true|false>`: Pad to preferred digits with trailing zeros when input is short (default: true). e.g. `"air"` -> `1840`, `"1234" --digits 5` -> `12340`
|
|
100
100
|
- `-h, --help`: show help
|
|
101
101
|
|
|
102
102
|
Examples:
|
|
@@ -123,7 +123,7 @@ A full Example:
|
|
|
123
123
|
{
|
|
124
124
|
// Preferred digit count for port (4 or 5)
|
|
125
125
|
"preferDigitCount": 5,
|
|
126
|
-
// Pad
|
|
126
|
+
// Pad to preferred digits with trailing zeros when input is short (default: true)
|
|
127
127
|
"paddingZero": true,
|
|
128
128
|
// Custom letter-to-digit mapping
|
|
129
129
|
"blockedPorts": [3000, 3001, 3002, 6666],
|
|
@@ -141,4 +141,4 @@ A full Example:
|
|
|
141
141
|
|
|
142
142
|
- 本仓库采用 pnpm monorepo;核心包位于 `packages/core`。
|
|
143
143
|
- 安装:在根目录执行 `pnpm install`。
|
|
144
|
-
- 运行测试:`pnpm -C packages/core test` 或 `pnpm -C packages/core test:watch`。
|
|
144
|
+
- 运行测试:`pnpm -C packages/core test` 或 `pnpm -C packages/core test:watch`。
|
package/locales/cn.json
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
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>
|
|
10
|
+
"optPadding": " --padding-zero <true|false> 输入不足位数时在末尾补 0 到偏好位数(默认:true)",
|
|
11
11
|
"optHelp": " -h, --help 显示帮助",
|
|
12
12
|
"examples": "示例:",
|
|
13
|
-
"ex1": " npx @lionad/port-key cfetch # ->
|
|
13
|
+
"ex1": " npx @lionad/port-key cfetch # -> 3435",
|
|
14
14
|
"ex2": " npx @lionad/port-key my-app-01 # -> 7610",
|
|
15
15
|
"ex3": " npx @lionad/port-key -d 5 -- 013344 # -> 13344(5位端口)",
|
|
16
16
|
"missingInput": "缺少输入文本。",
|
package/locales/en.json
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
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
|
|
10
|
+
"optPadding": " --padding-zero <true|false> Pad to preferred digits with trailing zeros when input is short (default: true)",
|
|
11
11
|
"optHelp": " -h, --help Show help",
|
|
12
12
|
"examples": "Examples:",
|
|
13
|
-
"ex1": " npx @lionad/port-key cfetch # ->
|
|
13
|
+
"ex1": " npx @lionad/port-key cfetch # -> 3435",
|
|
14
14
|
"ex2": " npx @lionad/port-key my-app-01 # -> 7610",
|
|
15
15
|
"ex3": " npx @lionad/port-key -d 5 -- 013344 # -> 13344 (5-digit port)",
|
|
16
16
|
"missingInput": "Missing input text.",
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -172,7 +172,6 @@ function runCli(argv, stdout = process.stdout, stderr = process.stderr, deps = {
|
|
|
172
172
|
blockedPorts: effective.blockedPorts,
|
|
173
173
|
minPort: effective.minPort,
|
|
174
174
|
maxPort: effective.maxPort,
|
|
175
|
-
preferredRanges: effective.preferredRanges,
|
|
176
175
|
preferDigitCount: effective.preferDigitCount,
|
|
177
176
|
paddingZero: effective.paddingZero,
|
|
178
177
|
});
|
package/src/config.js
CHANGED
|
@@ -45,18 +45,20 @@ function mergeConfig(base, override) {
|
|
|
45
45
|
const a = base && typeof base === 'object' ? base : {};
|
|
46
46
|
const b = override && typeof override === 'object' ? override : {};
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
const merged = {
|
|
49
49
|
...a,
|
|
50
50
|
...b,
|
|
51
51
|
map: b.map ?? a.map,
|
|
52
52
|
blockedPorts: b.blockedPorts ?? a.blockedPorts,
|
|
53
53
|
minPort: b.minPort ?? a.minPort,
|
|
54
54
|
maxPort: b.maxPort ?? a.maxPort,
|
|
55
|
-
preferredRanges: b.preferredRanges ?? a.preferredRanges,
|
|
56
55
|
preferDigitCount: b.preferDigitCount ?? a.preferDigitCount,
|
|
57
56
|
paddingZero: b.paddingZero ?? a.paddingZero,
|
|
58
57
|
lang: b.lang ?? a.lang,
|
|
59
58
|
};
|
|
59
|
+
// preferredRanges 是未实现的死字段,透传会让用户误以为配置生效,显式剔除
|
|
60
|
+
delete merged.preferredRanges;
|
|
61
|
+
return merged;
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
function getPortKeyDirPath(deps = {}) {
|
|
@@ -98,7 +100,6 @@ function loadRunCount(deps = {}) {
|
|
|
98
100
|
|
|
99
101
|
function incrementRunCount(deps = {}) {
|
|
100
102
|
const fsModule = deps.fs || fs;
|
|
101
|
-
const pathModule = deps.path || path;
|
|
102
103
|
const logPath = getLogPath(deps);
|
|
103
104
|
const portKeyDir = getPortKeyDirPath(deps);
|
|
104
105
|
|
package/src/i18n.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
5
6
|
|
|
6
7
|
const SUPPORTED = new Set(['en', 'cn']);
|
|
7
8
|
let cache = {};
|
|
@@ -16,7 +17,9 @@ function loadMessages(lang = 'cn', deps = {}) {
|
|
|
16
17
|
if (cache[l]) return cache[l];
|
|
17
18
|
const fsModule = deps.fs || fs;
|
|
18
19
|
const pathModule = deps.path || path;
|
|
19
|
-
|
|
20
|
+
// 用 fileURLToPath 取得真实文件路径,避免手工剥离 file:// 前缀在 Windows 上得到 /C:/... 这类非法路径
|
|
21
|
+
const moduleDir = pathModule.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const file = pathModule.join(pathModule.dirname(moduleDir), 'locales', `${l}.json`);
|
|
20
23
|
const raw = fsModule.readFileSync(file, 'utf8');
|
|
21
24
|
const json = JSON.parse(raw);
|
|
22
25
|
cache[l] = json;
|
package/src/port-key.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export type PickPortOptions = {
|
|
|
12
12
|
minPort?: number;
|
|
13
13
|
maxPort?: number;
|
|
14
14
|
blockedPorts?: ReadonlySet<number> | number[];
|
|
15
|
+
/** 短输入是否用 '0' 右补齐到 preferDigitCount 位,默认 true */
|
|
16
|
+
paddingZero?: boolean;
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
export const DEFAULT_MAP: Readonly<DigitLetterMap>;
|
package/src/port-key.js
CHANGED
|
@@ -63,11 +63,12 @@ function normalizeInput(text) {
|
|
|
63
63
|
function mapToDigits(text, map = DEFAULT_MAP) {
|
|
64
64
|
const reverse = buildReverseMap(map);
|
|
65
65
|
const input = normalizeInput(text);
|
|
66
|
+
const hasLetter = /[a-z]/.test(input);
|
|
66
67
|
|
|
67
68
|
let out = '';
|
|
68
69
|
for (const ch of input) {
|
|
69
70
|
if (ch >= '0' && ch <= '9') {
|
|
70
|
-
out += ch;
|
|
71
|
+
if (!hasLetter) out += ch;
|
|
71
72
|
continue;
|
|
72
73
|
}
|
|
73
74
|
|
|
@@ -97,60 +98,39 @@ function isPortBlocked(port, blockedPorts) {
|
|
|
97
98
|
function pickPortFromDigits(digits, options = {}) {
|
|
98
99
|
const raw = String(digits || '').replace(/[^0-9]/g, '');
|
|
99
100
|
if (!raw) return { port: null, reason: 'No digits found in input' };
|
|
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' };
|
|
106
101
|
|
|
107
102
|
const minPort = Number.isFinite(options.minPort) ? options.minPort : 0;
|
|
108
103
|
const maxPort = Number.isFinite(options.maxPort) ? options.maxPort : 65535;
|
|
109
104
|
const blockedPorts = options.blockedPorts || DEFAULT_BLOCKED_PORTS;
|
|
110
|
-
|
|
111
|
-
|
|
105
|
+
// 位数仅接受 2~5 的整数,其余一律回退默认 4 位,避免 1 位端口或小数窗口等无意义结果
|
|
106
|
+
const preferDigitCount =
|
|
107
|
+
Number.isInteger(options.preferDigitCount) && options.preferDigitCount >= 2 && options.preferDigitCount <= 5
|
|
108
|
+
? options.preferDigitCount
|
|
109
|
+
: 4;
|
|
110
|
+
const padToPreferredDigits =
|
|
111
|
+
options.padToPreferredDigits === undefined ? options.paddingZero !== false : Boolean(options.padToPreferredDigits);
|
|
112
112
|
|
|
113
113
|
const candidates = [];
|
|
114
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
115
|
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
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.
|
|
139
|
-
for (let len = Math.min(normalized.length, preferDigitCount); len >= 2; len -= 1) {
|
|
140
|
-
candidates.push(normalized.slice(0, len));
|
|
141
|
-
}
|
|
142
|
-
for (let len = Math.min(normalized.length, preferDigitCount); len >= 2; len -= 1) {
|
|
143
|
-
candidates.push(normalized.slice(normalized.length - len));
|
|
116
|
+
if (normalized.length >= preferDigitCount) {
|
|
117
|
+
for (let i = 0; i <= normalized.length - preferDigitCount; i += 1) {
|
|
118
|
+
candidates.push(normalized.slice(i, i + preferDigitCount));
|
|
144
119
|
}
|
|
120
|
+
} else if (padToPreferredDigits && normalized.length > 0) {
|
|
121
|
+
candidates.push(normalized.padEnd(preferDigitCount, '0'));
|
|
122
|
+
} else if (normalized.length >= 2) {
|
|
123
|
+
candidates.push(normalized);
|
|
145
124
|
}
|
|
146
|
-
|
|
147
|
-
// Merge padded candidates
|
|
148
|
-
candidates.push(...paddedCandidates);
|
|
149
125
|
|
|
150
126
|
const unique = Array.from(new Set(candidates));
|
|
151
127
|
const rejectedCandidates = [];
|
|
152
128
|
|
|
153
129
|
for (const c of unique) {
|
|
130
|
+
if (c.length > 1 && c.startsWith('0')) {
|
|
131
|
+
rejectedCandidates.push({ candidate: c, reason: 'Leading zero is not allowed' });
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
154
134
|
const port = Number.parseInt(c, 10);
|
|
155
135
|
if (!isValidPort(port)) {
|
|
156
136
|
rejectedCandidates.push({ candidate: c, reason: 'Invalid port number' });
|
|
@@ -167,6 +147,21 @@ function pickPortFromDigits(digits, options = {}) {
|
|
|
167
147
|
return { port, rejectedCandidates };
|
|
168
148
|
}
|
|
169
149
|
|
|
150
|
+
if (preferDigitCount === 5) {
|
|
151
|
+
const fallback = pickPortFromDigits(raw, { ...options, preferDigitCount: 4 });
|
|
152
|
+
if (fallback && fallback.port !== null) {
|
|
153
|
+
return {
|
|
154
|
+
port: fallback.port,
|
|
155
|
+
rejectedCandidates: rejectedCandidates.concat(fallback.rejectedCandidates || []),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
port: null,
|
|
160
|
+
reason: fallback.reason || 'No valid port could be generated from input',
|
|
161
|
+
rejectedCandidates: rejectedCandidates.concat(fallback.rejectedCandidates || []),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
170
165
|
return {
|
|
171
166
|
port: null,
|
|
172
167
|
reason: 'No valid port could be generated from input',
|