@lionad/port-key 0.4.2 → 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 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 short ports with zero (default: true). e.g. "air" -> 1840
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 short ports with zero (default: true)
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> 是否为短端口号补零(默认:true)",
10
+ "optPadding": " --padding-zero <true|false> 输入不足位数时在末尾补 0 到偏好位数(默认:true)",
11
11
  "optHelp": " -h, --help 显示帮助",
12
12
  "examples": "示例:",
13
- "ex1": " npx @lionad/port-key cfetch # -> 34353",
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 short ports with zero (default: true)",
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 # -> 34353",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lionad/port-key",
3
- "version": "0.4.2",
3
+ "version": "0.6.0",
4
4
  "description": "A simple, practical port naming strategy",
5
5
  "type": "module",
6
6
  "types": "./src/port-key.d.ts",
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
- return {
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
- const file = pathModule.join(pathModule.dirname(pathModule.dirname(import.meta.url.replace('file://', ''))), 'locales', `${l}.json`);
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
- const preferDigitCount = options.preferDigitCount || 4;
111
- // paddingZero is already defined above
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 (preferDigitCount && normalized.length >= preferDigitCount) {
133
- candidates.push(normalized.slice(0, preferDigitCount));
134
- candidates.push(normalized.slice(normalized.length - preferDigitCount));
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',