@lionad/port-key 0.1.2 → 0.1.4
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/LICENSE +0 -1
- package/bin/port-key.js +0 -0
- package/locales/cn.json +4 -1
- package/locales/en.json +4 -1
- package/package.json +5 -2
- package/src/cli.js +19 -3
- package/src/config.js +73 -8
- package/src/port-key.js +1 -10
- package/README.md +0 -91
package/LICENSE
CHANGED
|
@@ -5,4 +5,3 @@ Copyright (c) 2026 Lionad
|
|
|
5
5
|
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
|
6
6
|
|
|
7
7
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
8
|
-
|
package/bin/port-key.js
CHANGED
|
File without changes
|
package/locales/cn.json
CHANGED
|
@@ -18,5 +18,8 @@
|
|
|
18
18
|
"invalidDigits": "无效的位数。必须为 4 或 5。",
|
|
19
19
|
"missingLang": "缺少 --lang 的值",
|
|
20
20
|
"invalidLang": "不支持的语言。仅支持 \"en\" 或 \"cn\"。",
|
|
21
|
-
"noValidPort": "无法从输入生成有效端口。"
|
|
21
|
+
"noValidPort": "无法从输入生成有效端口。",
|
|
22
|
+
"configReadFailed": "[info] 配置文件 {path} 似乎无法读取或不是有效的 JSON 格式。将使用默认配置。",
|
|
23
|
+
"firstRunNoConfig": "[info] 欢迎使用 PortKey!\n\n你可以在 {configDir} 目录下创建 config.json 文件来自定义配置。\n\n配置示例请参考:\n{readmeUrl}",
|
|
24
|
+
"readmeConfigExampleLink": "https://github.com/Lionad-Morotar/port-key#config"
|
|
22
25
|
}
|
package/locales/en.json
CHANGED
|
@@ -18,5 +18,8 @@
|
|
|
18
18
|
"invalidDigits": "Invalid digit count. Must be 4 or 5.",
|
|
19
19
|
"missingLang": "Missing value for --lang",
|
|
20
20
|
"invalidLang": "Unsupported language. Only \"en\" or \"cn\" are available.",
|
|
21
|
-
"noValidPort": "No valid port could be generated from input."
|
|
21
|
+
"noValidPort": "No valid port could be generated from input.",
|
|
22
|
+
"configReadFailed": "[info] It seems the config file at {path} could not be read or is not valid JSON. Using default config.",
|
|
23
|
+
"firstRunNoConfig": "[info] Welcome to PortKey!\n\nYou can create a config.json file in {configDir} to customize your settings.\n\nFor config examples, see:\n{readmeUrl}",
|
|
24
|
+
"readmeConfigExampleLink": "https://github.com/Lionad-Morotar/port-key#config"
|
|
22
25
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lionad/port-key",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "A simple, practical port naming strategy",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -46,5 +46,8 @@
|
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"vitest": "4.0.16"
|
|
48
48
|
},
|
|
49
|
-
"dependencies": {}
|
|
49
|
+
"dependencies": {},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"registry": "https://registry.npmjs.org/"
|
|
52
|
+
}
|
|
50
53
|
}
|
package/src/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
import { DEFAULT_MAP, mapToPort, parseUserMap } from './port-key.js';
|
|
4
|
-
import { loadUserConfigSync, mergeConfig } from './config.js';
|
|
4
|
+
import { loadUserConfigSync, mergeConfig, getPortKeyDirPath, loadRunCount, incrementRunCount } from './config.js';
|
|
5
5
|
import { getLangOrDefault, loadMessages } from './i18n.js';
|
|
6
6
|
|
|
7
7
|
function formatHelp(lang = 'cn') {
|
|
@@ -78,7 +78,6 @@ function parseArgv(argv) {
|
|
|
78
78
|
continue;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
// Unknown option: treat as positional (so "portkey -foo" still works when user passes "--")
|
|
82
81
|
positionals.push(token);
|
|
83
82
|
i += 1;
|
|
84
83
|
}
|
|
@@ -93,7 +92,24 @@ function parseArgv(argv) {
|
|
|
93
92
|
}
|
|
94
93
|
|
|
95
94
|
function runCli(argv, stdout = process.stdout, stderr = process.stderr, deps = {}) {
|
|
96
|
-
const { config } = loadUserConfigSync(deps);
|
|
95
|
+
const { config, configReadError, path: configPath, configExists } = loadUserConfigSync(deps);
|
|
96
|
+
const { isFirstRun } = loadRunCount(deps);
|
|
97
|
+
|
|
98
|
+
if (configReadError && configPath) {
|
|
99
|
+
const lang = getLangOrDefault((config && config.lang) || 'cn');
|
|
100
|
+
const MSG = loadMessages(lang);
|
|
101
|
+
stderr.write(MSG.configReadFailed.replace('{path}', configPath) + '\n');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (isFirstRun && !configExists) {
|
|
105
|
+
const lang = getLangOrDefault((config && config.lang) || 'cn');
|
|
106
|
+
const MSG = loadMessages(lang);
|
|
107
|
+
const portKeyDir = getPortKeyDirPath(deps);
|
|
108
|
+
stderr.write(MSG.firstRunNoConfig.replace('{configDir}', portKeyDir || '~/.port-key').replace('{readmeUrl}', MSG.readmeConfigExampleLink) + '\n');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
incrementRunCount(deps);
|
|
112
|
+
|
|
97
113
|
let parsed;
|
|
98
114
|
try {
|
|
99
115
|
parsed = parseArgv(argv);
|
package/src/config.js
CHANGED
|
@@ -11,7 +11,6 @@ function getConfigPath(pathModule, osModule, env) {
|
|
|
11
11
|
const newPath = pathToUse.join(home, '.port-key', 'config.json');
|
|
12
12
|
const oldPath = pathToUse.join(home, '.portkey', 'config.json');
|
|
13
13
|
try {
|
|
14
|
-
// Prefer new path if exists; otherwise fall back to old path
|
|
15
14
|
if (fs.existsSync(newPath)) return newPath;
|
|
16
15
|
return oldPath;
|
|
17
16
|
} catch {
|
|
@@ -26,20 +25,19 @@ function loadUserConfigSync(deps = {}) {
|
|
|
26
25
|
const env = deps.env || process.env;
|
|
27
26
|
|
|
28
27
|
const configPath = getConfigPath(pathModule, osModule, env);
|
|
29
|
-
if (!configPath) return { path: null, config: {} };
|
|
28
|
+
if (!configPath) return { path: null, config: {}, configReadError: false, configExists: false };
|
|
30
29
|
|
|
31
30
|
try {
|
|
32
|
-
if (!fsModule.existsSync(configPath)) return { path: configPath, config: {} };
|
|
31
|
+
if (!fsModule.existsSync(configPath)) return { path: configPath, config: {}, configReadError: false, configExists: false };
|
|
33
32
|
const raw = fsModule.readFileSync(configPath, 'utf8');
|
|
34
|
-
if (!String(raw || '').trim()) return { path: configPath, config: {} };
|
|
33
|
+
if (!String(raw || '').trim()) return { path: configPath, config: {}, configReadError: false, configExists: true };
|
|
35
34
|
const parsed = JSON.parse(raw);
|
|
36
35
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
37
|
-
return { path: configPath, config: {} };
|
|
36
|
+
return { path: configPath, config: {}, configReadError: true, configExists: true };
|
|
38
37
|
}
|
|
39
|
-
return { path: configPath, config: parsed };
|
|
38
|
+
return { path: configPath, config: parsed, configReadError: false, configExists: true };
|
|
40
39
|
} catch {
|
|
41
|
-
|
|
42
|
-
return { path: configPath, config: {} };
|
|
40
|
+
return { path: configPath, config: {}, configReadError: true, configExists: true };
|
|
43
41
|
}
|
|
44
42
|
}
|
|
45
43
|
|
|
@@ -60,8 +58,75 @@ function mergeConfig(base, override) {
|
|
|
60
58
|
};
|
|
61
59
|
}
|
|
62
60
|
|
|
61
|
+
function getPortKeyDirPath(deps = {}) {
|
|
62
|
+
const osModule = deps.os || os;
|
|
63
|
+
const pathModule = deps.path || path;
|
|
64
|
+
const env = deps.env || process.env;
|
|
65
|
+
|
|
66
|
+
const home = (env && (env.PORTKEY_HOME || env.HOME)) || (osModule && osModule.homedir && osModule.homedir());
|
|
67
|
+
if (!home) return null;
|
|
68
|
+
|
|
69
|
+
const pathToUse = pathModule || path;
|
|
70
|
+
return pathToUse.join(home, '.port-key');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getLogPath(deps = {}) {
|
|
74
|
+
const portKeyDir = getPortKeyDirPath(deps);
|
|
75
|
+
if (!portKeyDir) return null;
|
|
76
|
+
const pathModule = deps.path || path;
|
|
77
|
+
return pathModule.join(portKeyDir, 'log.json');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function loadRunCount(deps = {}) {
|
|
81
|
+
const fsModule = deps.fs || fs;
|
|
82
|
+
const logPath = getLogPath(deps);
|
|
83
|
+
if (!logPath) return { count: 0, isFirstRun: true, logPath: null };
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
if (!fsModule.existsSync(logPath)) {
|
|
87
|
+
return { count: 0, isFirstRun: true, logPath };
|
|
88
|
+
}
|
|
89
|
+
const raw = fsModule.readFileSync(logPath, 'utf8');
|
|
90
|
+
const parsed = JSON.parse(raw);
|
|
91
|
+
const count = typeof parsed?.count === 'number' ? parsed.count : 0;
|
|
92
|
+
return { count, isFirstRun: count === 0, logPath };
|
|
93
|
+
} catch {
|
|
94
|
+
return { count: 0, isFirstRun: true, logPath };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function incrementRunCount(deps = {}) {
|
|
99
|
+
const fsModule = deps.fs || fs;
|
|
100
|
+
const pathModule = deps.path || path;
|
|
101
|
+
const logPath = getLogPath(deps);
|
|
102
|
+
const portKeyDir = getPortKeyDirPath(deps);
|
|
103
|
+
|
|
104
|
+
if (!logPath || !portKeyDir) return;
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
if (!fsModule.existsSync(portKeyDir)) {
|
|
108
|
+
fsModule.mkdirSync(portKeyDir, { recursive: true });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let count = 0;
|
|
112
|
+
if (fsModule.existsSync(logPath)) {
|
|
113
|
+
const raw = fsModule.readFileSync(logPath, 'utf8');
|
|
114
|
+
const parsed = JSON.parse(raw);
|
|
115
|
+
count = typeof parsed?.count === 'number' ? parsed.count : 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
count += 1;
|
|
119
|
+
fsModule.writeFileSync(logPath, JSON.stringify({ count }, null, 2), 'utf8');
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
63
124
|
export {
|
|
64
125
|
getConfigPath,
|
|
65
126
|
loadUserConfigSync,
|
|
66
127
|
mergeConfig,
|
|
128
|
+
getPortKeyDirPath,
|
|
129
|
+
getLogPath,
|
|
130
|
+
loadRunCount,
|
|
131
|
+
incrementRunCount,
|
|
67
132
|
};
|
package/src/port-key.js
CHANGED
|
@@ -16,7 +16,6 @@ const DEFAULT_MAP = Object.freeze({
|
|
|
16
16
|
const DEFAULT_BLOCKED_PORTS = Object.freeze(
|
|
17
17
|
new Set([
|
|
18
18
|
0,
|
|
19
|
-
// common/system-ish
|
|
20
19
|
20,
|
|
21
20
|
21,
|
|
22
21
|
22,
|
|
@@ -39,7 +38,6 @@ const DEFAULT_BLOCKED_PORTS = Object.freeze(
|
|
|
39
38
|
636,
|
|
40
39
|
993,
|
|
41
40
|
995,
|
|
42
|
-
// very common dev defaults
|
|
43
41
|
3000,
|
|
44
42
|
3001,
|
|
45
43
|
5000,
|
|
@@ -51,7 +49,6 @@ const DEFAULT_BLOCKED_PORTS = Object.freeze(
|
|
|
51
49
|
9000,
|
|
52
50
|
27017,
|
|
53
51
|
3306,
|
|
54
|
-
// common dev port
|
|
55
52
|
1234,
|
|
56
53
|
])
|
|
57
54
|
);
|
|
@@ -125,16 +122,14 @@ function pickPortFromDigits(digits, options = {}) {
|
|
|
125
122
|
const minPort = Number.isFinite(options.minPort) ? options.minPort : 0;
|
|
126
123
|
const maxPort = Number.isFinite(options.maxPort) ? options.maxPort : 65535;
|
|
127
124
|
const blockedPorts = options.blockedPorts || DEFAULT_BLOCKED_PORTS;
|
|
128
|
-
const preferDigitCount = options.preferDigitCount || 4;
|
|
125
|
+
const preferDigitCount = options.preferDigitCount || 4;
|
|
129
126
|
|
|
130
|
-
// Prefer exact digit count using normalized prefix first, then normalized suffix
|
|
131
127
|
const candidates = [];
|
|
132
128
|
const normalized = raw.replace(/^0+/, '');
|
|
133
129
|
if (preferDigitCount && normalized.length >= preferDigitCount) {
|
|
134
130
|
candidates.push(normalized.slice(0, preferDigitCount));
|
|
135
131
|
candidates.push(normalized.slice(normalized.length - preferDigitCount));
|
|
136
132
|
} else {
|
|
137
|
-
// Fallback only when digits are fewer than preferDigitCount
|
|
138
133
|
for (let len = Math.min(normalized.length, preferDigitCount); len >= 2; len -= 1) {
|
|
139
134
|
candidates.push(normalized.slice(0, len));
|
|
140
135
|
}
|
|
@@ -180,7 +175,6 @@ function parseUserMap(mapString) {
|
|
|
180
175
|
const raw = String(mapString || '').trim();
|
|
181
176
|
if (!raw) throw new Error('Empty map string');
|
|
182
177
|
|
|
183
|
-
// 1) Strict JSON: {"1":"qaz",...}
|
|
184
178
|
try {
|
|
185
179
|
const maybe = JSON.parse(raw);
|
|
186
180
|
if (!isPlainObject(maybe)) throw new Error('Map must be an object');
|
|
@@ -200,11 +194,8 @@ function parseUserMap(mapString) {
|
|
|
200
194
|
if (err.message === 'Keys must be digits, values must be mapped letters') {
|
|
201
195
|
throw err;
|
|
202
196
|
}
|
|
203
|
-
// fall through
|
|
204
197
|
}
|
|
205
198
|
|
|
206
|
-
// 2) JS-ish object literal: { 1: 'qaz', 0: 'p' }
|
|
207
|
-
// Extract digit keys and quoted string values.
|
|
208
199
|
const extracted = {};
|
|
209
200
|
const re = /([0-9])\s*:\s*(['"])(.*?)\2/g;
|
|
210
201
|
let match;
|
package/README.md
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
# PortKey
|
|
2
|
-
|
|
3
|
-
<p align="center">
|
|
4
|
-
<img width="200" src="/public/logo.png" />
|
|
5
|
-
</p>
|
|
6
|
-
|
|
7
|
-
<p align="center">
|
|
8
|
-
<strong>PortKey:A Simple, Practical Port Naming Strategy</strong>
|
|
9
|
-
</p>
|
|
10
|
-
|
|
11
|
-
## Brief
|
|
12
|
-
|
|
13
|
-
Generate ports with a letter-to-number keyboard mapping
|
|
14
|
-
|
|
15
|
-
When you’re running a bunch of projects locally, picking port numbers becomes annoying.
|
|
16
|
-
|
|
17
|
-
- Over the last couple of years, there have been *so many* new projects. To really try them out, you often need to boot them locally—and then ports start colliding.
|
|
18
|
-
- If you want to keep browser tabs (or bookmarks) stable, a project’s port shouldn’t keep changing.
|
|
19
|
-
|
|
20
|
-
For example, I have more than ten Nuxt apps on my machine. If they all default to `3000`, that’s obviously not going to work. So I came up with a simple, consistent port naming rule to “assign” ports per project.
|
|
21
|
-
|
|
22
|
-
[Source Blog Post](https://lionad.art/articles/simple-naming-method)
|
|
23
|
-
|
|
24
|
-
### Core idea
|
|
25
|
-
|
|
26
|
-
Instead of picking random numbers, map the **project name to numbers based on the keyboard**, so the port is *readable* and *memorable*.
|
|
27
|
-
|
|
28
|
-
As long as the result is within the valid port range (**0–65535**) and doesn’t hit reserved/system ports, you can just use it.
|
|
29
|
-
|
|
30
|
-
More specifically: using a standard QWERTY keyboard, map each letter to a single digit based on its **row/column position**.
|
|
31
|
-
|
|
32
|
-
Example:
|
|
33
|
-
|
|
34
|
-
`"cfetch"` → `c(3) f(4) e(3) t(5) c(3) h(6)` → `34353`(port number)
|
|
35
|
-
|
|
36
|
-
Then you can take the first 4 digits (e.g. `3453`), or keep more digits (e.g. `34353`). Either is fine.
|
|
37
|
-
|
|
38
|
-
If a project needs multiple ports (frontend, backend, database, etc.), pick **one** of these two approaches:
|
|
39
|
-
|
|
40
|
-
1. Use the project prefix, then append a “role suffix”
|
|
41
|
-
- For `"cfetch"`, take `3435` as the base
|
|
42
|
-
- Frontend (`fe`, i.e. `43`) → `34354`
|
|
43
|
-
- Backend (`server`) → `34352`
|
|
44
|
-
- Database (`mongo`) → `34357`
|
|
45
|
-
- …and so on
|
|
46
|
-
|
|
47
|
-
2. Use the project prefix, then assign sequential roles
|
|
48
|
-
- For `"cfetch"`, take `3435` as the base
|
|
49
|
-
- Web → `34351`
|
|
50
|
-
- Backend → `34352`
|
|
51
|
-
- Database → `34353`
|
|
52
|
-
- …and so on
|
|
53
|
-
|
|
54
|
-
### Valid port range
|
|
55
|
-
|
|
56
|
-
- Ports must be within **0–65535**.
|
|
57
|
-
- For custom services, it’s usually best to use **1024–49151** (non-reserved) or **49152–65535** (private/dynamic).
|
|
58
|
-
- As long as the mapped number stays under the limit, it’s valid.
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
## How to use
|
|
63
|
-
|
|
64
|
-
```
|
|
65
|
-
npx @lionad/port-key <your-project-name>
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### CLI options
|
|
69
|
-
|
|
70
|
-
- `-m, --map <object>`: custom mapping (JSON or JS-like object literal)
|
|
71
|
-
- `--lang <code>`: output language (currently only `en` and `cn`, default: `cn`)
|
|
72
|
-
- `-d, --digits <count>`: preferred digit count for port (4 or 5, default: 4)
|
|
73
|
-
- `-h, --help`: show help
|
|
74
|
-
|
|
75
|
-
Examples:
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
npx @lionad/port-key cfetch # -> 3435
|
|
79
|
-
npx @lionad/port-key cfetch --digits 4 # -> 3435 (4-digit port)
|
|
80
|
-
npx @lionad/port-key cfetch --digits 5 # -> 34353 (5-digit port)
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
Notes:
|
|
84
|
-
- Default log language is `cn`. Use `--lang en` to show English messages.
|
|
85
|
-
- Use `-h` or `--help` to show help.
|
|
86
|
-
|
|
87
|
-
### Config
|
|
88
|
-
|
|
89
|
-
PortKey reads optional user config from:
|
|
90
|
-
|
|
91
|
-
- `~/.port-key/config.json`
|