@wwkit/opm 1.0.1
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 +381 -0
- package/bin/index.js +9 -0
- package/docs/managers/apt.md +45 -0
- package/docs/managers/brew.md +92 -0
- package/docs/managers/bun.md +50 -0
- package/docs/managers/cft.md +57 -0
- package/docs/managers/composer.md +96 -0
- package/docs/managers/config.md +32 -0
- package/docs/managers/dnf.md +46 -0
- package/docs/managers/node.md +49 -0
- package/docs/managers/npm.md +100 -0
- package/docs/managers/nvm.md +34 -0
- package/docs/managers/php.md +48 -0
- package/docs/managers/phpenv.md +34 -0
- package/docs/managers/pip.md +98 -0
- package/docs/managers/python.md +53 -0
- package/docs/managers/uv.md +27 -0
- package/docs/opm.html +570 -0
- package/docs/packages/opencode.md +41 -0
- package/package.json +51 -0
- package/scripts/postinstall.js +7 -0
- package/src/binaries/base.js +508 -0
- package/src/binaries/cft/index.js +35 -0
- package/src/binaries/depchecker.js +172 -0
- package/src/binaries/exefinder.js +110 -0
- package/src/binaries/platform.js +17 -0
- package/src/cli/doc.js +335 -0
- package/src/cli/groups/config.js +78 -0
- package/src/cli/groups/ensure.js +96 -0
- package/src/cli/groups/homebrew.js +73 -0
- package/src/cli/groups/nvm.js +73 -0
- package/src/cli/groups/package.js +514 -0
- package/src/cli/groups/phpenv.js +89 -0
- package/src/cli/groups/uv.js +73 -0
- package/src/cli/groups/version-manager.js +186 -0
- package/src/cli/helpers/args.js +49 -0
- package/src/cli/index.js +129 -0
- package/src/config.js +133 -0
- package/src/config.json5 +158 -0
- package/src/formatter.js +13 -0
- package/src/harnesses/base.js +205 -0
- package/src/harnesses/opencode/clear.js +239 -0
- package/src/harnesses/opencode/db.js +39 -0
- package/src/harnesses/opencode/index.js +46 -0
- package/src/harnesses/opencode/sqlite.js +200 -0
- package/src/index.js +46 -0
- package/src/managers/apt.js +377 -0
- package/src/managers/base.js +167 -0
- package/src/managers/brew.js +374 -0
- package/src/managers/bun.js +342 -0
- package/src/managers/composer.js +280 -0
- package/src/managers/dnf.js +370 -0
- package/src/managers/http.js +432 -0
- package/src/managers/node.js +150 -0
- package/src/managers/npm.js +473 -0
- package/src/managers/php.js +157 -0
- package/src/managers/pip.js +555 -0
- package/src/managers/python.js +180 -0
- package/src/managers/runtime.js +309 -0
- package/src/managers/winget.js +268 -0
- package/src/presets.js +24 -0
- package/src/proxy-presets.js +12 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* winget(Windows Package Manager)包管理器实现(Windows)
|
|
3
|
+
*
|
|
4
|
+
* 通过执行 winget CLI 命令实现统一接口(系统级,无用户级/global 概念)。
|
|
5
|
+
* winget 预装于 Windows 10/11(App Installer),是非 Windows 平台的 brew/apt 对应物。
|
|
6
|
+
*
|
|
7
|
+
* 源查询使用 winget search,本地查询使用 winget list,详情使用 winget show。
|
|
8
|
+
* winget 无镜像/registry 概念(包源由 winget source 管理),故 registry 动作只读不写。
|
|
9
|
+
*
|
|
10
|
+
* 输出为定宽文本表格,通过表头列位置解析(_parseTable)。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { execSync, spawnSync } from 'node:child_process'
|
|
14
|
+
import { PackageManager } from './base.js'
|
|
15
|
+
|
|
16
|
+
export class WingetManager extends PackageManager {
|
|
17
|
+
constructor() {
|
|
18
|
+
super()
|
|
19
|
+
this.name = 'winget'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 执行 winget 命令,返回 stdout
|
|
24
|
+
* @param {string[]} args
|
|
25
|
+
* @param {{ allowNonZero?: boolean }} [opts]
|
|
26
|
+
* @returns {string}
|
|
27
|
+
* @private
|
|
28
|
+
*/
|
|
29
|
+
_exec(args, opts = {}) {
|
|
30
|
+
try {
|
|
31
|
+
return execSync(['winget', ...args].join(' '), {
|
|
32
|
+
encoding: 'utf8',
|
|
33
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
34
|
+
shell: true,
|
|
35
|
+
})
|
|
36
|
+
} catch (err) {
|
|
37
|
+
if (opts.allowNonZero) {
|
|
38
|
+
return err.stdout ? err.stdout.toString() : ''
|
|
39
|
+
}
|
|
40
|
+
const stderr = err.stderr ? err.stderr.toString().trim() : ''
|
|
41
|
+
if (stderr) {
|
|
42
|
+
throw new Error(stderr)
|
|
43
|
+
}
|
|
44
|
+
return err.stdout ? err.stdout.toString() : ''
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 执行 winget 命令,输出直接继承到终端(用于 install/uninstall/upgrade)
|
|
50
|
+
* @param {string[]} args
|
|
51
|
+
* @private
|
|
52
|
+
*/
|
|
53
|
+
_execInherit(args) {
|
|
54
|
+
const result = spawnSync('winget', args, { stdio: 'inherit', shell: true })
|
|
55
|
+
if (result.error) {
|
|
56
|
+
throw new Error(result.error.message)
|
|
57
|
+
}
|
|
58
|
+
if (result.status !== 0) {
|
|
59
|
+
throw new Error(`winget ${args.join(' ')} exited with code ${result.status}`)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 解析 winget 定宽表格输出为行数组
|
|
65
|
+
*
|
|
66
|
+
* 通过表头行定位各列起始位置,再按列宽切分数据行。
|
|
67
|
+
* 支持可选列(如 Available 仅在 list --upgrade-available 时出现)。
|
|
68
|
+
*
|
|
69
|
+
* @param {string} out
|
|
70
|
+
* @returns {{ Name?: string, Id?: string, Version?: string, Available?: string, Source?: string }[]}
|
|
71
|
+
* @private
|
|
72
|
+
*/
|
|
73
|
+
_parseTable(out) {
|
|
74
|
+
const lines = out.split('\n').filter((l) => l.trim())
|
|
75
|
+
if (lines.length < 2) return []
|
|
76
|
+
|
|
77
|
+
const headerIdx = lines.findIndex((l) => /Name.*Id.*Version/i.test(l))
|
|
78
|
+
if (headerIdx < 0) return []
|
|
79
|
+
|
|
80
|
+
const header = lines[headerIdx]
|
|
81
|
+
const cols = {}
|
|
82
|
+
for (const key of ['Name', 'Id', 'Version', 'Available', 'Source']) {
|
|
83
|
+
const idx = header.indexOf(key)
|
|
84
|
+
if (idx >= 0) cols[key] = idx
|
|
85
|
+
}
|
|
86
|
+
const sorted = Object.entries(cols).sort((a, b) => a[1] - b[1])
|
|
87
|
+
|
|
88
|
+
const results = []
|
|
89
|
+
for (let i = headerIdx + 1; i < lines.length; i++) {
|
|
90
|
+
const line = lines[i]
|
|
91
|
+
if (!line.trim() || line.startsWith('-')) continue
|
|
92
|
+
const row = {}
|
|
93
|
+
for (let j = 0; j < sorted.length; j++) {
|
|
94
|
+
const [key, start] = sorted[j]
|
|
95
|
+
const end = j + 1 < sorted.length ? sorted[j + 1][1] : line.length
|
|
96
|
+
row[key] = line.slice(start, end).trim()
|
|
97
|
+
}
|
|
98
|
+
if (row.Id) results.push(row)
|
|
99
|
+
}
|
|
100
|
+
return results
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 解析 winget show 的 key: value 输出
|
|
105
|
+
* @param {string} out
|
|
106
|
+
* @returns {Record<string, string>}
|
|
107
|
+
* @private
|
|
108
|
+
*/
|
|
109
|
+
_parseShow(out) {
|
|
110
|
+
const info = {}
|
|
111
|
+
for (const line of out.split('\n')) {
|
|
112
|
+
const m = line.match(/^\s*(\S+)\s*:\s*(.*)$/)
|
|
113
|
+
if (m) info[m[1].toLowerCase()] = m[2].trim()
|
|
114
|
+
}
|
|
115
|
+
return info
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async getVersion() {
|
|
119
|
+
try {
|
|
120
|
+
const out = this._exec(['--version'], { allowNonZero: true }).trim()
|
|
121
|
+
const firstLine = out.split('\n')[0] || ''
|
|
122
|
+
const match = firstLine.match(/v?(\d+\.\d+\.\d+)/)
|
|
123
|
+
if (match) {
|
|
124
|
+
return { name: 'winget', installed: true, version: match[1] }
|
|
125
|
+
}
|
|
126
|
+
if (firstLine) {
|
|
127
|
+
return { name: 'winget', installed: true, version: firstLine }
|
|
128
|
+
}
|
|
129
|
+
} catch {}
|
|
130
|
+
return {
|
|
131
|
+
name: 'winget',
|
|
132
|
+
version: '',
|
|
133
|
+
installed: false,
|
|
134
|
+
hint: 'winget is built into Windows 10/11 (App Installer). Not available on macOS/Linux.',
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async getRegistry() {
|
|
139
|
+
let sources = ''
|
|
140
|
+
try {
|
|
141
|
+
sources = this._exec(['source', 'list'], { allowNonZero: true }).trim()
|
|
142
|
+
} catch {}
|
|
143
|
+
return { registry: '(winget sources — read-only)', sources }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async setRegistry() {
|
|
147
|
+
throw new Error(
|
|
148
|
+
"winget has no mirror/registry concept; manage sources via 'winget source add/remove' instead"
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async searchPackage(name, version, opts = {}) {
|
|
153
|
+
const out = this._exec(
|
|
154
|
+
['search', name, '--accept-source-agreements', '--disable-interactivity'],
|
|
155
|
+
{ allowNonZero: true }
|
|
156
|
+
)
|
|
157
|
+
const rows = this._parseTable(out)
|
|
158
|
+
if (rows.length === 0) {
|
|
159
|
+
if (version) return { name, version, exists: false, available: false, registry: '' }
|
|
160
|
+
return { name, exists: false, registry: '' }
|
|
161
|
+
}
|
|
162
|
+
const row = rows[0]
|
|
163
|
+
const latest = row.Version || 'unknown'
|
|
164
|
+
if (version) {
|
|
165
|
+
return { name, version, exists: true, available: latest === version, registry: '' }
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
name: row.Name || name,
|
|
169
|
+
id: row.Id,
|
|
170
|
+
exists: true,
|
|
171
|
+
latest,
|
|
172
|
+
versions: [latest],
|
|
173
|
+
registry: '',
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async isInstalled(name, opts = {}) {
|
|
178
|
+
const out = this._exec(
|
|
179
|
+
['list', name, '--accept-source-agreements', '--disable-interactivity'],
|
|
180
|
+
{ allowNonZero: true }
|
|
181
|
+
)
|
|
182
|
+
const rows = this._parseTable(out)
|
|
183
|
+
const hit = rows.find((r) => r.Id === name || r.Name === name) || rows[0]
|
|
184
|
+
if (hit) {
|
|
185
|
+
return {
|
|
186
|
+
name: hit.Name || name,
|
|
187
|
+
id: hit.Id,
|
|
188
|
+
installed: true,
|
|
189
|
+
version: hit.Version || 'unknown',
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return { name, installed: false }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async packageInfo(name, opts = {}) {
|
|
196
|
+
const out = this._exec(['show', name, '--accept-source-agreements'], { allowNonZero: true })
|
|
197
|
+
const info = this._parseShow(out)
|
|
198
|
+
if (!info.packageidentifier) {
|
|
199
|
+
return { name, description: '(not found)' }
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
name: info.packageidentifier,
|
|
203
|
+
description: info.description || '',
|
|
204
|
+
latest: info.packageversion || '',
|
|
205
|
+
homepage: info.homepage || '',
|
|
206
|
+
license: info.license || '',
|
|
207
|
+
author: info.publisher || '',
|
|
208
|
+
repository: info.publisherurl || '',
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async packageVersions(name, opts = {}) {
|
|
213
|
+
const info = await this.packageInfo(name, opts)
|
|
214
|
+
return {
|
|
215
|
+
name,
|
|
216
|
+
versions: info.latest ? [info.latest] : [],
|
|
217
|
+
registry: '',
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async listInstalled(opts = {}) {
|
|
222
|
+
const out = this._exec(
|
|
223
|
+
['list', '--accept-source-agreements', '--disable-interactivity'],
|
|
224
|
+
{ allowNonZero: true }
|
|
225
|
+
)
|
|
226
|
+
return this._parseTable(out).map((r) => ({
|
|
227
|
+
name: r.Name || r.Id,
|
|
228
|
+
id: r.Id,
|
|
229
|
+
version: r.Version || 'unknown',
|
|
230
|
+
source: r.Source || '',
|
|
231
|
+
}))
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async listOutdated(opts = {}) {
|
|
235
|
+
const out = this._exec(
|
|
236
|
+
['list', '--upgrade-available', '--accept-source-agreements', '--disable-interactivity'],
|
|
237
|
+
{ allowNonZero: true }
|
|
238
|
+
)
|
|
239
|
+
return this._parseTable(out).map((r) => ({
|
|
240
|
+
name: r.Name || r.Id,
|
|
241
|
+
id: r.Id,
|
|
242
|
+
current: r.Version || '',
|
|
243
|
+
latest: r.Available || '',
|
|
244
|
+
source: r.Source || '',
|
|
245
|
+
}))
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async install(name, version, opts = {}) {
|
|
249
|
+
const args = ['install', name, '--accept-package-agreements', '--accept-source-agreements']
|
|
250
|
+
if (version) args.push('--version', version)
|
|
251
|
+
this._execInherit(args)
|
|
252
|
+
return { name, version: version || 'latest', action: 'installed' }
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async uninstall(name, opts = {}) {
|
|
256
|
+
this._execInherit(['uninstall', name, '--accept-source-agreements'])
|
|
257
|
+
return { name, action: 'uninstalled' }
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async upgrade(name, opts = {}) {
|
|
261
|
+
this._execInherit(['upgrade', name, '--accept-package-agreements', '--accept-source-agreements'])
|
|
262
|
+
return { name, version: 'latest', action: 'upgraded' }
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async cleanCache() {
|
|
266
|
+
return { action: 'winget has no cache clean command' }
|
|
267
|
+
}
|
|
268
|
+
}
|
package/src/presets.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 镜像预设(从合并配置派生)
|
|
3
|
+
*
|
|
4
|
+
* 数据源为 src/config.json5(含用户合并层),这里提供常量与便捷函数。
|
|
5
|
+
* registry set 写回的是 config.json5 的 active,而非系统配置。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getPresets as _getPresets, resolveRegistryInput as _resolveRegistryInput } from './config.js'
|
|
9
|
+
|
|
10
|
+
export const NPM_PRESETS = _getPresets('npm')
|
|
11
|
+
export const PIP_PRESETS = _getPresets('pip')
|
|
12
|
+
export const DNF_PRESETS = _getPresets('dnf')
|
|
13
|
+
export const APT_PRESETS = _getPresets('apt')
|
|
14
|
+
export const BREW_PRESETS = _getPresets('brew')
|
|
15
|
+
export const COMPOSER_PRESETS = _getPresets('composer')
|
|
16
|
+
export const BUN_PRESETS = _getPresets('bun')
|
|
17
|
+
export const NVM_PRESETS = _getPresets('nvm')
|
|
18
|
+
export const NODE_PRESETS = _getPresets('node')
|
|
19
|
+
export const PHPENV_PRESETS = _getPresets('phpenv')
|
|
20
|
+
export const PHP_PRESETS = _getPresets('php')
|
|
21
|
+
export const PYTHON_PRESETS = _getPresets('python')
|
|
22
|
+
|
|
23
|
+
export const getPresets = _getPresets
|
|
24
|
+
export const resolveRegistryInput = _resolveRegistryInput
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 代理预设(从合并配置派生,仅供展示;active 不计入预设)
|
|
3
|
+
*
|
|
4
|
+
* 数据源为 src/config.json5 的 proxy 字段。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getConfig } from './config.js'
|
|
8
|
+
|
|
9
|
+
const _proxy = { ...(getConfig().proxy ?? {}) }
|
|
10
|
+
delete _proxy.active
|
|
11
|
+
|
|
12
|
+
export const PROXY_PRESETS = Object.freeze(_proxy)
|