@wwkit/opm 1.0.13 → 1.0.15
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 +32 -0
- package/package.json +13 -4
- package/src/binaries/base.js +1 -1
- package/src/cli/groups/env.js +451 -0
- package/src/cli/groups/ping.js +87 -26
- package/src/cli/index.js +2 -0
- package/src/harnesses/opencode/index.js +4 -3
- package/src/index.js +2 -1
- package/src/managers/brew.js +1 -0
- package/src/managers/bun.js +7 -34
- package/src/managers/composer.js +1 -1
- package/src/managers/pip.js +1 -1
- package/src/managers/proc.js +7 -1
- package/src/tools/webwork/index.js +31 -16
- package/src/managers/http.js +0 -432
package/README.md
CHANGED
|
@@ -67,6 +67,7 @@ pnpm opm <command> [args] [options]
|
|
|
67
67
|
| `ftp <action>` | FTP 文件管理(上传/rsync/下载) |
|
|
68
68
|
| `ensure <runtime> [version]` | 确保运行时已安装(node/php/python) |
|
|
69
69
|
| `ping <host|proxy|registry>` | 验证主机、代理或所有 registry 的可访问性 |
|
|
70
|
+
| `env <action>` | 环境变量管理(list/get/set,跨平台:Unix profile / Windows user 级注册表) |
|
|
70
71
|
| `config [section]` | 查看常用配置(proxy + 各 registry 段,段名动态生成) |
|
|
71
72
|
| `opencode <action>` | opencode 运维命令(安装/升级/清理/env);裸 `opm opencode` 启动 TUI |
|
|
72
73
|
| `harness <action>` | @wwkit/harness 插件管理(安装/升级/list/status/env/dir) |
|
|
@@ -532,6 +533,37 @@ opm ping registry -d # 输出完整 JSON 数组
|
|
|
532
533
|
> `opm ping proxy` 从 `proxy.active` 解析代理 URL,提取主机名后 ping。
|
|
533
534
|
> `opm ping registry` 遍历所有含 `registry` 配置的工具,并行 ping 各 active 地址的主机。
|
|
534
535
|
|
|
536
|
+
### env
|
|
537
|
+
|
|
538
|
+
```bash
|
|
539
|
+
opm env list # 列出 opm 管理的环境变量(含配置文件路径)
|
|
540
|
+
opm env list --all # 追加 process.env 全量,每条标注 managed: true/false
|
|
541
|
+
opm env get PATH # 根据 key 取值(先查 opm 管理源,回退 process.env)
|
|
542
|
+
opm env set FOO bar # 设置环境变量(替换已存在的)
|
|
543
|
+
opm env set MY_API_KEY secret123 # 设置 API 密钥
|
|
544
|
+
opm env search api # 按关键词匹配 key 或 value(忽略大小写)
|
|
545
|
+
opm env search api --all # 同时搜索 process.env
|
|
546
|
+
opm env help # 通用介绍
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
输出格式:默认输出**简化文本**(list/search 按来源分组:`配置文件路径` 行 + `key = value` 行;get/set 输出单行 `key = value`);加 `-j/--json` 输出完整 JSON。
|
|
550
|
+
|
|
551
|
+
```bash
|
|
552
|
+
opm env list # 简化文本(按来源分组)
|
|
553
|
+
opm env list --json # JSON 数组
|
|
554
|
+
opm env get PATH # 简化:PATH = /usr/bin:/bin
|
|
555
|
+
opm env get PATH --json # JSON 对象(含 source/scope)
|
|
556
|
+
opm env set FOO bar # 简化:FOO = bar
|
|
557
|
+
opm env set FOO bar --json # JSON 对象(含 written/source)
|
|
558
|
+
opm env search api # 简化:匹配项按来源分组
|
|
559
|
+
opm env search api --json # JSON 数组
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
> `list`/`search` 默认只作用于 `opm env set` 管理的变量(Unix 的 `# >>> wwkit NAME >>>` 标记块 / Windows manifest);`--all` 在其基础上追加 `process.env` 全量,并给每条标注 `managed: true/false` 与来源。
|
|
563
|
+
> `search <keyword>` 匹配 key 或 value 是否包含关键词(忽略大小写),可用 `-q/--query` 替代位置参数。
|
|
564
|
+
> 跨平台:Unix 读写 shell profile(`.zshrc`/`.bashrc`/`.profile`/fish config),用 `# >>> wwkit NAME >>>` 标记块管理;Windows 用 `setx` 写 user 级注册表(`HKCU\Environment`),`list` 用 `reg query` 读取。
|
|
565
|
+
> Windows 上 `setx` 不留标记,opm 通过 `~/.config/opm/env-managed.json` 记录哪些 user 级变量由 opm 管理;`set` 始终写入 user 级(无需管理员权限)。
|
|
566
|
+
|
|
535
567
|
### opencode
|
|
536
568
|
|
|
537
569
|
```bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wwkit/opm",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"author": "bluesliu <langcai163@163.com>",
|
|
5
5
|
"description": "Unified CLI — package management (npm/pip/dnf/apt) + config view + opencode maintenance",
|
|
6
6
|
"type": "module",
|
|
@@ -35,7 +35,16 @@
|
|
|
35
35
|
"basic-ftp": "^6.2.1",
|
|
36
36
|
"extract-zip": "^2.0.1",
|
|
37
37
|
"systeminformation": "^5.23.0",
|
|
38
|
-
"@wwkit/shared": "1.0.
|
|
38
|
+
"@wwkit/shared": "1.0.20"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"jest": "^29.7.0"
|
|
42
|
+
},
|
|
43
|
+
"jest": {
|
|
44
|
+
"transform": {},
|
|
45
|
+
"testMatch": [
|
|
46
|
+
"**/tests/**/*.test.js"
|
|
47
|
+
]
|
|
39
48
|
},
|
|
40
49
|
"publishConfig": {
|
|
41
50
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -45,8 +54,8 @@
|
|
|
45
54
|
"postinstall": "node scripts/postinstall.js",
|
|
46
55
|
"check": "node --check bin/index.js",
|
|
47
56
|
"start": "node bin/index.js",
|
|
48
|
-
"test": "node --
|
|
49
|
-
"test:all": "node --
|
|
57
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --no-cache tests",
|
|
58
|
+
"test:all": "node --experimental-vm-modules node_modules/jest/bin/jest.js --no-cache tests",
|
|
50
59
|
"doc": "node -e \"import('./src/cli/doc.js').then(m => m.generateDocHtml({ open: false }))\"",
|
|
51
60
|
"release": "node ../../packages/shared/bin/release-pkg.js"
|
|
52
61
|
}
|
package/src/binaries/base.js
CHANGED
|
@@ -17,7 +17,7 @@ import path from 'node:path'
|
|
|
17
17
|
import { parseFlags } from '../cli/helpers/args.js'
|
|
18
18
|
import { output } from '../formatter.js'
|
|
19
19
|
import { getActiveRegistry, setActiveRegistry, getPresets, getActiveProxy, getUserConfigDir } from '../config.js'
|
|
20
|
-
import { fetchText, downloadToFile } from '
|
|
20
|
+
import { fetchText, downloadToFile } from '@wwkit/shared'
|
|
21
21
|
import { getOs, OS_TYPES } from './platform.js'
|
|
22
22
|
import { find as findExe } from './exefinder.js'
|
|
23
23
|
import { verify as verifyDeps } from './depchecker.js'
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* env 命令组 — 跨平台环境变量便捷管理
|
|
3
|
+
*
|
|
4
|
+
* 用法:
|
|
5
|
+
* opm env list [--all] 列出 opm 管理的环境变量(含配置文件路径);--all 追加 process.env 全量
|
|
6
|
+
* opm env get <key> 根据 key 取值(先查 opm 管理的配置源,回退 process.env)
|
|
7
|
+
* opm env set <key> <value> 设置值(替换已存在的)
|
|
8
|
+
* opm env help 通用介绍
|
|
9
|
+
*
|
|
10
|
+
* 跨平台策略(复用 @wwkit/shared 的 Shell):
|
|
11
|
+
* Unix: 读写 shell profile(bash/zsh/fish),用 `# >>> wwkit NAME >>>` 标记块管理
|
|
12
|
+
* Windows: setx 写 user 级注册表;list 用 reg query 读取;用 manifest 文件
|
|
13
|
+
* (~/.config/opm/env-managed.json)识别哪些 user 级变量由 opm 管理
|
|
14
|
+
* (setx 不留标记,无法从注册表本身区分)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'node:fs'
|
|
18
|
+
import path from 'node:path'
|
|
19
|
+
import { spawnSync } from 'node:child_process'
|
|
20
|
+
import { Shell } from '@wwkit/shared'
|
|
21
|
+
import { parseFlags } from '../helpers/args.js'
|
|
22
|
+
import { output } from '../../formatter.js'
|
|
23
|
+
import { getUserConfigDir } from '../../config.js'
|
|
24
|
+
|
|
25
|
+
/** Windows 注册表环境变量键路径 */
|
|
26
|
+
const WIN_USER_KEY = 'HKCU\\Environment'
|
|
27
|
+
|
|
28
|
+
/** manifest 文件名(Windows 记录 opm 管理的 user 级变量 key) */
|
|
29
|
+
const MANIFEST_FILE = 'env-managed.json'
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 反转 Shell.setEnvVar 的转义([$`\\"] → \\$&)
|
|
33
|
+
* @param {string} s
|
|
34
|
+
* @returns {string}
|
|
35
|
+
*/
|
|
36
|
+
function unescapeEnvValue(s) {
|
|
37
|
+
return s.replace(/\\([$`\\"])/g, '$1')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 从一行 export/set 语句中提取变量值
|
|
42
|
+
* @param {string} line
|
|
43
|
+
* @param {string} key
|
|
44
|
+
* @returns {string|null}
|
|
45
|
+
*/
|
|
46
|
+
function parseEnvLine(line, key) {
|
|
47
|
+
const exportQuoted = line.match(new RegExp(`^export\\s+${key}="(.*)"\\s*$`))
|
|
48
|
+
if (exportQuoted) return unescapeEnvValue(exportQuoted[1])
|
|
49
|
+
const fishQuoted = line.match(new RegExp(`^set -gx\\s+${key}\\s+"(.*)"\\s*$`))
|
|
50
|
+
if (fishQuoted) return unescapeEnvValue(fishQuoted[1])
|
|
51
|
+
const exportBare = line.match(new RegExp(`^export\\s+${key}=(\\S+)\\s*$`))
|
|
52
|
+
if (exportBare) return exportBare[1]
|
|
53
|
+
const fishBare = line.match(new RegExp(`^set -gx\\s+${key}\\s+(\\S+)\\s*$`))
|
|
54
|
+
if (fishBare) return fishBare[1]
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 解析 profile 文件内容中的 wwkit 标记块,提取 opm 管理的环境变量
|
|
60
|
+
* @param {string} content
|
|
61
|
+
* @returns {{ key: string, value: string }[]}
|
|
62
|
+
*/
|
|
63
|
+
export function parseEnvBlocks(content) {
|
|
64
|
+
if (!content) return []
|
|
65
|
+
const lines = content.split('\n')
|
|
66
|
+
const result = []
|
|
67
|
+
let i = 0
|
|
68
|
+
while (i < lines.length) {
|
|
69
|
+
const marker = lines[i].match(/^# >>> wwkit (\S+) >>>/)
|
|
70
|
+
if (marker) {
|
|
71
|
+
const key = marker[1]
|
|
72
|
+
// 块体:标记行之后到下一个 wwkit 标记或 EOF 之间的非空非注释行
|
|
73
|
+
let j = i + 1
|
|
74
|
+
while (j < lines.length && lines[j].trim() === '') j++
|
|
75
|
+
if (j < lines.length && !lines[j].startsWith('# >>> wwkit')) {
|
|
76
|
+
const value = parseEnvLine(lines[j], key)
|
|
77
|
+
if (value !== null) result.push({ key, value })
|
|
78
|
+
}
|
|
79
|
+
i = j + 1
|
|
80
|
+
} else {
|
|
81
|
+
i++
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return result
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 解析 Windows `reg query` 输出,提取环境变量条目
|
|
89
|
+
* @param {string} output
|
|
90
|
+
* @returns {{ key: string, type: string, value: string }[]}
|
|
91
|
+
*/
|
|
92
|
+
export function parseRegOutput(output) {
|
|
93
|
+
if (!output) return []
|
|
94
|
+
const result = []
|
|
95
|
+
for (const line of output.split(/\r?\n/)) {
|
|
96
|
+
const m = line.match(/^\s+(\S+)\s+(REG_\S+)\s+(.*)$/)
|
|
97
|
+
if (m) result.push({ key: m[1], type: m[2], value: m[3] })
|
|
98
|
+
}
|
|
99
|
+
return result
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 校验环境变量名合法性(字母/下划线开头,后接字母数字下划线)
|
|
104
|
+
* @param {string} key
|
|
105
|
+
* @returns {boolean}
|
|
106
|
+
*/
|
|
107
|
+
export function isValidEnvKey(key) {
|
|
108
|
+
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(key)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export class EnvGroup {
|
|
112
|
+
/**
|
|
113
|
+
* @param {{ shell?: Shell, configDir?: string }} [opts] - shell/configDir 注入用于测试
|
|
114
|
+
*/
|
|
115
|
+
constructor(opts = {}) {
|
|
116
|
+
this.name = 'env'
|
|
117
|
+
this.desc = 'Manage environment variables (list/get/set) across platforms'
|
|
118
|
+
this.shell = opts.shell || new Shell()
|
|
119
|
+
this.configDir = opts.configDir || ''
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** @returns {string} manifest 文件绝对路径 */
|
|
123
|
+
_manifestPath() {
|
|
124
|
+
const dir = this.configDir || getUserConfigDir()
|
|
125
|
+
return path.join(dir, MANIFEST_FILE)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 读取 manifest 中的 opm 管理 key 集合(Windows 用)
|
|
130
|
+
* @returns {Set<string>}
|
|
131
|
+
*/
|
|
132
|
+
_readManifest() {
|
|
133
|
+
try {
|
|
134
|
+
const raw = fs.readFileSync(this._manifestPath(), 'utf8')
|
|
135
|
+
const data = JSON.parse(raw)
|
|
136
|
+
return new Set(Array.isArray(data.keys) ? data.keys : [])
|
|
137
|
+
} catch {
|
|
138
|
+
return new Set()
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 将 key 登记到 manifest(Windows set 时调用)
|
|
144
|
+
* @param {string} key
|
|
145
|
+
*/
|
|
146
|
+
_addManagedKey(key) {
|
|
147
|
+
const keys = this._readManifest()
|
|
148
|
+
keys.add(key)
|
|
149
|
+
const dir = this.configDir || getUserConfigDir()
|
|
150
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
151
|
+
fs.writeFileSync(this._manifestPath(), JSON.stringify({ keys: [...keys] }, null, 2), 'utf8')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 列出 opm 管理的环境变量(默认 list 的数据源)
|
|
156
|
+
* Unix: 解析所有 profile 的 wwkit 标记块
|
|
157
|
+
* Windows: 读 manifest → 查 user 级注册表当前值
|
|
158
|
+
* @returns {object[]}
|
|
159
|
+
*/
|
|
160
|
+
_managedItems() {
|
|
161
|
+
return this.shell.isWindows ? this._listWindowsManaged() : this._listUnix()
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
_listUnix() {
|
|
165
|
+
const items = []
|
|
166
|
+
for (const { path: profilePath, syntax } of this.shell.getProfiles()) {
|
|
167
|
+
let content = ''
|
|
168
|
+
try {
|
|
169
|
+
content = fs.readFileSync(profilePath, 'utf8')
|
|
170
|
+
} catch {}
|
|
171
|
+
for (const { key, value } of parseEnvBlocks(content)) {
|
|
172
|
+
items.push({ key, value, scope: 'user', source: profilePath, syntax })
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return items
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
_listWindowsManaged() {
|
|
179
|
+
const managedKeys = this._readManifest()
|
|
180
|
+
if (managedKeys.size === 0) return []
|
|
181
|
+
const userVars = this._regQuery(WIN_USER_KEY)
|
|
182
|
+
const byKey = new Map(userVars.map((v) => [v.key, v]))
|
|
183
|
+
const items = []
|
|
184
|
+
for (const key of managedKeys) {
|
|
185
|
+
const v = byKey.get(key)
|
|
186
|
+
if (v) {
|
|
187
|
+
items.push({ key, value: v.value, scope: 'user', source: 'registry (user)', type: v.type })
|
|
188
|
+
} else {
|
|
189
|
+
// manifest 登记过但注册表中已不存在(外部删除)——标注缺失
|
|
190
|
+
items.push({ key, value: null, scope: 'user', source: 'registry (user)', missing: true })
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return items
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
_regQuery(keyPath) {
|
|
197
|
+
try {
|
|
198
|
+
const result = spawnSync('reg', ['query', keyPath], {
|
|
199
|
+
encoding: 'utf8',
|
|
200
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
201
|
+
})
|
|
202
|
+
if (result.status !== 0) return []
|
|
203
|
+
return parseRegOutput(result.stdout || '')
|
|
204
|
+
} catch {
|
|
205
|
+
return []
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* --all:opm 管理的 ∪ process.env 全量,每条标注 managed
|
|
211
|
+
* @returns {object[]}
|
|
212
|
+
*/
|
|
213
|
+
_listAll() {
|
|
214
|
+
const managedItems = this._managedItems()
|
|
215
|
+
const seen = new Set()
|
|
216
|
+
const items = []
|
|
217
|
+
for (const m of managedItems) {
|
|
218
|
+
items.push({ key: m.key, value: m.value, managed: true, source: m.source, scope: m.scope })
|
|
219
|
+
seen.add(m.key)
|
|
220
|
+
}
|
|
221
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
222
|
+
if (!seen.has(key)) {
|
|
223
|
+
items.push({ key, value, managed: false, source: 'process.env' })
|
|
224
|
+
seen.add(key)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return items
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async run(argv) {
|
|
231
|
+
const [action, ...rest] = argv
|
|
232
|
+
|
|
233
|
+
if (!action || action === '-h' || action === '--help' || action === 'help') {
|
|
234
|
+
this.printHelp()
|
|
235
|
+
return
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const parsed = parseFlags(rest)
|
|
239
|
+
|
|
240
|
+
switch (action) {
|
|
241
|
+
case 'list':
|
|
242
|
+
return this._list(parsed)
|
|
243
|
+
case 'get':
|
|
244
|
+
return this._get(parsed)
|
|
245
|
+
case 'set':
|
|
246
|
+
return this._set(parsed)
|
|
247
|
+
case 'search':
|
|
248
|
+
return this._search(parsed)
|
|
249
|
+
default:
|
|
250
|
+
console.error(`Unknown action: ${action}`)
|
|
251
|
+
this.printHelp()
|
|
252
|
+
process.exit(1)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* 是否输出 JSON(--json / -j)
|
|
258
|
+
* @param {{ flags: object, positional: string[] }} parsed
|
|
259
|
+
* @returns {boolean}
|
|
260
|
+
*/
|
|
261
|
+
_useJson(parsed) {
|
|
262
|
+
return parsed.flags.json === 'true' || parsed.flags.j === 'true'
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* 统一输出:--json 走 JSON,否则走 plain 格式化函数
|
|
267
|
+
* @param {{ flags: object, positional: string[] }} parsed
|
|
268
|
+
* @param {*} data - JSON 输出数据
|
|
269
|
+
* @param {() => string} plain - 简化字符串输出函数
|
|
270
|
+
*/
|
|
271
|
+
_out(parsed, data, plain) {
|
|
272
|
+
if (this._useJson(parsed)) {
|
|
273
|
+
output(data)
|
|
274
|
+
} else {
|
|
275
|
+
console.log(plain())
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* list 简化格式:按 source 分组,每组标题为来源,下面 key = value 行
|
|
281
|
+
* @param {object[]} items
|
|
282
|
+
* @returns {string}
|
|
283
|
+
*/
|
|
284
|
+
_formatListPlain(items) {
|
|
285
|
+
const groups = []
|
|
286
|
+
const groupMap = new Map()
|
|
287
|
+
for (const item of items) {
|
|
288
|
+
const source = item.source || 'unknown'
|
|
289
|
+
if (!groupMap.has(source)) {
|
|
290
|
+
const g = { source, entries: [] }
|
|
291
|
+
groupMap.set(source, g)
|
|
292
|
+
groups.push(g)
|
|
293
|
+
}
|
|
294
|
+
groupMap.get(source).entries.push(item)
|
|
295
|
+
}
|
|
296
|
+
return groups
|
|
297
|
+
.map((g) => [
|
|
298
|
+
g.source,
|
|
299
|
+
...g.entries.map((e) => `${e.key} = ${e.value === null || e.value === undefined ? '(not set)' : e.value}`),
|
|
300
|
+
].join('\n'))
|
|
301
|
+
.join('\n\n')
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* 列出环境变量
|
|
306
|
+
* 默认: 只显示 opm 管理的(Unix 标记块 / Windows manifest)
|
|
307
|
+
* --all: 追加 process.env 全量,每条标注 managed: true/false
|
|
308
|
+
* 输出: 默认简化文本(按 source 分组),--json 输出 JSON
|
|
309
|
+
* @param {{ flags: object, positional: string[] }} parsed
|
|
310
|
+
*/
|
|
311
|
+
async _list(parsed) {
|
|
312
|
+
const all = parsed.flags.all === 'true' || parsed.flags.a === 'true'
|
|
313
|
+
const items = all ? this._listAll() : this._managedItems()
|
|
314
|
+
this._out(parsed, items, () => this._formatListPlain(items))
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* 根据 key 取值:先查 opm 管理的配置源,回退 process.env
|
|
319
|
+
* 输出: 默认 `key = value`(未找到 `key = (not found)`),--json 输出 JSON
|
|
320
|
+
* @param {{ flags: object, positional: string[] }} parsed
|
|
321
|
+
*/
|
|
322
|
+
async _get(parsed) {
|
|
323
|
+
const key = parsed.positional[0] || parsed.flags.k || parsed.flags.key || ''
|
|
324
|
+
if (!key) {
|
|
325
|
+
console.error('Usage: opm env get <key>')
|
|
326
|
+
console.error('Missing required <key>')
|
|
327
|
+
process.exit(1)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const configured = this._managedItems()
|
|
331
|
+
const hit = configured.find((e) => e.key === key)
|
|
332
|
+
if (hit) {
|
|
333
|
+
this._out(parsed, { key, value: hit.value, found: true, source: hit.source, scope: hit.scope }, () => `${key} = ${hit.value}`)
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (process.env[key] !== undefined) {
|
|
338
|
+
const value = process.env[key]
|
|
339
|
+
this._out(parsed, { key, value, found: true, source: 'process.env' }, () => `${key} = ${value}`)
|
|
340
|
+
return
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
this._out(parsed, { key, value: null, found: false }, () => `${key} = (not found)`)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* 设置环境变量(替换已存在的)
|
|
348
|
+
* Unix: 写入 shell profile 标记块;Windows: setx 写 user 级注册表 + manifest 登记
|
|
349
|
+
* @param {{ flags: object, positional: string[] }} parsed
|
|
350
|
+
*/
|
|
351
|
+
async _set(parsed) {
|
|
352
|
+
const key = parsed.positional[0] || parsed.flags.k || parsed.flags.key || ''
|
|
353
|
+
const value = parsed.positional[1] ?? parsed.flags.v ?? parsed.flags.value ?? ''
|
|
354
|
+
|
|
355
|
+
if (!key) {
|
|
356
|
+
console.error('Usage: opm env set <key> <value>')
|
|
357
|
+
console.error('Missing required <key>')
|
|
358
|
+
process.exit(1)
|
|
359
|
+
}
|
|
360
|
+
if (!isValidEnvKey(key)) {
|
|
361
|
+
console.error(`Invalid env key: "${key}" (must start with letter/underscore, only letters/digits/underscore)`)
|
|
362
|
+
process.exit(1)
|
|
363
|
+
}
|
|
364
|
+
if (parsed.positional.length < 2 && !(parsed.flags.v || parsed.flags.value)) {
|
|
365
|
+
console.error('Usage: opm env set <key> <value>')
|
|
366
|
+
console.error('Missing required <value>')
|
|
367
|
+
process.exit(1)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const written = this.shell.setEnvVar(key, value)
|
|
371
|
+
if (this.shell.isWindows) {
|
|
372
|
+
this._addManagedKey(key)
|
|
373
|
+
}
|
|
374
|
+
const source = this.shell.isWindows
|
|
375
|
+
? 'registry (user)'
|
|
376
|
+
: this.shell.getProfiles().map((p) => p.path)
|
|
377
|
+
const result = { key, value, written, source }
|
|
378
|
+
this._out(parsed, result, () => `${key} = ${value}${written ? '' : ' (no change)'}`)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* 按关键词搜索环境变量(匹配 key 或 value,忽略大小写)
|
|
383
|
+
* 默认: 在 opm 管理的变量中搜索;--all 扩展至 process.env 全量
|
|
384
|
+
* 输出: 默认简化文本(按 source 分组),--json 输出 JSON
|
|
385
|
+
* @param {{ flags: object, positional: string[] }} parsed
|
|
386
|
+
*/
|
|
387
|
+
async _search(parsed) {
|
|
388
|
+
const keyword = parsed.positional[0] || parsed.flags.q || parsed.flags.query || ''
|
|
389
|
+
if (!keyword) {
|
|
390
|
+
console.error('Usage: opm env search <keyword>')
|
|
391
|
+
console.error('Missing required <keyword>')
|
|
392
|
+
process.exit(1)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const all = parsed.flags.all === 'true' || parsed.flags.a === 'true'
|
|
396
|
+
const source = all ? this._listAll() : this._managedItems()
|
|
397
|
+
const kw = keyword.toLowerCase()
|
|
398
|
+
const matches = source.filter((e) => {
|
|
399
|
+
const k = String(e.key).toLowerCase()
|
|
400
|
+
const v = e.value === null || e.value === undefined ? '' : String(e.value).toLowerCase()
|
|
401
|
+
return k.includes(kw) || v.includes(kw)
|
|
402
|
+
})
|
|
403
|
+
this._out(parsed, matches, () => this._formatListPlain(matches))
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
printHelp() {
|
|
407
|
+
console.log(`
|
|
408
|
+
Usage: opm env <action> [args] [options]
|
|
409
|
+
|
|
410
|
+
Actions:
|
|
411
|
+
list [--all] List opm-managed env vars with their config file path
|
|
412
|
+
(default: only vars set via "opm env set";
|
|
413
|
+
--all appends all process.env vars, each tagged managed: true/false)
|
|
414
|
+
get <key> Get value by key (checks opm-managed sources first, then process.env)
|
|
415
|
+
set <key> <value> Set value, replacing any existing one
|
|
416
|
+
(Unix: shell profile marker block; Windows: setx user-level registry)
|
|
417
|
+
search <keyword> [--all] Search env vars whose key or value contains keyword (case-insensitive)
|
|
418
|
+
(default: opm-managed vars; --all also searches process.env)
|
|
419
|
+
help Show this help
|
|
420
|
+
|
|
421
|
+
Options:
|
|
422
|
+
-k, --key <name> Key (alternative to positional for get/set)
|
|
423
|
+
-v, --value <val> Value (alternative to positional for set)
|
|
424
|
+
-q, --query <kw> Keyword (alternative to positional for search)
|
|
425
|
+
-a, --all Also list all process.env vars (tagged managed: true/false)
|
|
426
|
+
-j, --json Output JSON (default: plain text)
|
|
427
|
+
-h, --help Show this help
|
|
428
|
+
|
|
429
|
+
Output:
|
|
430
|
+
default (no -j) Plain text: list groups by source then "key = value" lines;
|
|
431
|
+
get/set print a single "key = value" line
|
|
432
|
+
-j / --json JSON object/array with full metadata
|
|
433
|
+
|
|
434
|
+
Platform behavior:
|
|
435
|
+
Unix read/write shell profiles (.zshrc/.bashrc/.profile/fish config)
|
|
436
|
+
via "# >>> wwkit NAME >>>" marker blocks
|
|
437
|
+
Windows read/write user-level registry (HKCU\\Environment); writes via setx;
|
|
438
|
+
opm-managed keys tracked in ~/.config/opm/env-managed.json
|
|
439
|
+
|
|
440
|
+
Examples:
|
|
441
|
+
opm env list
|
|
442
|
+
opm env list --all # all process.env vars, tagged managed
|
|
443
|
+
opm env get PATH
|
|
444
|
+
opm env set FOO bar
|
|
445
|
+
opm env set MY_API_KEY secret123
|
|
446
|
+
opm env search api # match key or value containing "api" (case-insensitive)
|
|
447
|
+
opm env search api --all # also search process.env
|
|
448
|
+
opm env help
|
|
449
|
+
`)
|
|
450
|
+
}
|
|
451
|
+
}
|