@twinsearth/dsh-2xray 0.2.4 → 0.2.5
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/CHANGELOG.md +7 -0
- package/lib/binary.js +81 -2
- package/package.json +1 -1
- package/test/binary.test.js +28 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.5 — 2026-10-29
|
|
4
|
+
|
|
5
|
+
### 修复
|
|
6
|
+
|
|
7
|
+
- **binary-missing 自动恢复**:未配置 `binary.path` 时,会按 `V2RAYN_HOME/V2RAYN_PATH`、`%LOCALAPPDATA%`、桌面/下载、Program Files、盘符根目录做有界搜索(只进入名字含 v2ray 的目录,3s 预算),识别 v2rayN 7.x 的 `bin/xray/xray.exe` 并直接借用。
|
|
8
|
+
- 找不到时错误信息仍列出显式候选与 PATH,便于诊断。
|
|
9
|
+
|
|
3
10
|
## 0.2.4 — 2026-10-29
|
|
4
11
|
|
|
5
12
|
### 设置页与导入
|
package/lib/binary.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { accessSync, constants, existsSync } from 'node:fs'
|
|
1
|
+
import { accessSync, constants, existsSync, readdirSync } from 'node:fs'
|
|
2
2
|
import { delimiter, join } from 'node:path'
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
4
|
|
|
@@ -54,6 +54,8 @@ export async function resolveBinary({
|
|
|
54
54
|
launcher,
|
|
55
55
|
exists = isExecutable,
|
|
56
56
|
packageRoot = PACKAGE_ROOT,
|
|
57
|
+
discoveryRoots,
|
|
58
|
+
discoveryOptions = {},
|
|
57
59
|
}) {
|
|
58
60
|
const candidates = []
|
|
59
61
|
const explicit = config.binary.path?.trim()
|
|
@@ -90,14 +92,91 @@ export async function resolveBinary({
|
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
94
|
|
|
95
|
+
// v2rayN / v2rayN-core ship xray under their own directory (7.x uses
|
|
96
|
+
// bin/xray/xray.exe). Borrowing it avoids asking the user to copy a binary.
|
|
97
|
+
const discovered = discoverXray({ roots: discoveryRoots, ...discoveryOptions })
|
|
98
|
+
if (discovered.length > 0) {
|
|
99
|
+
return { path: discovered[0], source: 'discovered' }
|
|
100
|
+
}
|
|
101
|
+
|
|
93
102
|
throw new BinaryError(
|
|
94
103
|
`找不到 xray-core 可执行文件。已尝试:${candidates.map((candidate) => candidate.path).join(', ') || '(无显式路径)'},以及 PATH 中的 ${pathEntries.join(delimiter)}。` +
|
|
95
104
|
'请把 xray 放进插件自己的 bin/、数据目录的 bin/,或设置 config.binary.path。',
|
|
96
105
|
'binary-missing',
|
|
97
|
-
candidates.map((candidate) => candidate.path),
|
|
106
|
+
[...candidates.map((candidate) => candidate.path), ...discovered],
|
|
98
107
|
)
|
|
99
108
|
}
|
|
100
109
|
|
|
110
|
+
/** Common install roots for a borrowed xray binary (Windows-first). */
|
|
111
|
+
export function defaultDiscoveryRoots() {
|
|
112
|
+
const home = process.env.USERPROFILE ?? process.env.HOME ?? ''
|
|
113
|
+
const local = process.env.LOCALAPPDATA ?? ''
|
|
114
|
+
const roots = [
|
|
115
|
+
process.env.V2RAYN_HOME,
|
|
116
|
+
process.env.V2RAYN_PATH,
|
|
117
|
+
local,
|
|
118
|
+
home ? join(home, 'Desktop') : '',
|
|
119
|
+
home ? join(home, 'Downloads') : '',
|
|
120
|
+
'C:\\Program Files',
|
|
121
|
+
'C:\\Program Files (x86)',
|
|
122
|
+
'D:\\',
|
|
123
|
+
'E:\\',
|
|
124
|
+
'C:\\',
|
|
125
|
+
]
|
|
126
|
+
return [...new Set(roots.filter((root) => typeof root === 'string' && root.length > 0))]
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Bounded search for a v2rayN-style xray layout. Directory names must look
|
|
131
|
+
* like v2rayN, so the walk does not crawl unrelated trees; the time budget
|
|
132
|
+
* keeps a failed start from stalling activation.
|
|
133
|
+
*/
|
|
134
|
+
export function discoverXray({ roots = defaultDiscoveryRoots(), timeBudgetMs = 3000, max = 5 } = {}) {
|
|
135
|
+
const found = []
|
|
136
|
+
const deadline = Date.now() + timeBudgetMs
|
|
137
|
+
const skipped = /^(windows|programdata|recovery|\$recycle\.bin|system volume information|node_modules|\.git|appdata|temp)$/i
|
|
138
|
+
const layouts = [
|
|
139
|
+
['bin', 'xray', 'xray.exe'],
|
|
140
|
+
['bin', 'xray', 'xray'],
|
|
141
|
+
['bin', 'xray.exe'],
|
|
142
|
+
['bin', 'xray'],
|
|
143
|
+
['xray.exe'],
|
|
144
|
+
['xray'],
|
|
145
|
+
]
|
|
146
|
+
const check = (dir) => {
|
|
147
|
+
for (const parts of layouts) {
|
|
148
|
+
const candidate = join(dir, ...parts)
|
|
149
|
+
if (existsSync(candidate)) {
|
|
150
|
+
if (!found.includes(candidate)) found.push(candidate)
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const visit = (dir, depth) => {
|
|
156
|
+
if (found.length >= max || Date.now() > deadline) return
|
|
157
|
+
let entries
|
|
158
|
+
try {
|
|
159
|
+
entries = readdirSync(dir, { withFileTypes: true })
|
|
160
|
+
} catch {
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
for (const entry of entries) {
|
|
164
|
+
if (found.length >= max || Date.now() > deadline) return
|
|
165
|
+
if (!entry.isDirectory()) continue
|
|
166
|
+
const name = entry.name
|
|
167
|
+
if (skipped.test(name)) continue
|
|
168
|
+
const full = join(dir, name)
|
|
169
|
+
if (/v2ray/i.test(name)) check(full)
|
|
170
|
+
if (depth < 3) visit(full, depth + 1)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
for (const root of roots) {
|
|
174
|
+
if (found.length >= max || Date.now() > deadline) break
|
|
175
|
+
if (existsSync(root)) visit(root, 0)
|
|
176
|
+
}
|
|
177
|
+
return found
|
|
178
|
+
}
|
|
179
|
+
|
|
101
180
|
/**
|
|
102
181
|
* Run `xray version` and parse the version line. A failed probe (missing
|
|
103
182
|
* dynamic library, wrong architecture, antivirus block) is a startup failure,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twinsearth/dsh-2xray",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "DSH-2Xray: supervises xray-core and routes DSH process-wide HTTP(S) traffic through its local inbound, with optional multi-node gRPC control plane",
|
|
6
6
|
"type": "module",
|
package/test/binary.test.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import test from 'node:test'
|
|
2
2
|
import assert from 'node:assert/strict'
|
|
3
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
3
5
|
import { join } from 'node:path'
|
|
4
6
|
import { BinaryError, resolveBinary } from '../lib/binary.js'
|
|
5
7
|
import { normalizeConfig } from '../lib/config.js'
|
|
@@ -89,6 +91,7 @@ test('a missing binary fails with every candidate listed', async () => {
|
|
|
89
91
|
packageRoot,
|
|
90
92
|
exists: () => false,
|
|
91
93
|
launcher: {},
|
|
94
|
+
discoveryRoots: [],
|
|
92
95
|
}),
|
|
93
96
|
(error) => {
|
|
94
97
|
assert.ok(error instanceof BinaryError)
|
|
@@ -101,3 +104,28 @@ test('a missing binary fails with every candidate listed', async () => {
|
|
|
101
104
|
process.env.PATH = previousPath
|
|
102
105
|
}
|
|
103
106
|
})
|
|
107
|
+
|
|
108
|
+
test('discovers a v2rayN layout when nothing else resolves', async () => {
|
|
109
|
+
const dir = mkdtempSync(join(tmpdir(), 'dsh-xray-discover-'))
|
|
110
|
+
const previousPath = process.env.PATH
|
|
111
|
+
try {
|
|
112
|
+
const layout = join(dir, 'v2rayN-windows-64', 'bin', 'xray')
|
|
113
|
+
mkdirSync(layout, { recursive: true })
|
|
114
|
+
writeFileSync(join(layout, 'xray.exe'), '')
|
|
115
|
+
process.env.PATH = ''
|
|
116
|
+
const found = await resolveBinary({
|
|
117
|
+
config: config(),
|
|
118
|
+
dataDir: join(dir, 'data'),
|
|
119
|
+
packageRoot: join(dir, 'plugin'),
|
|
120
|
+
exists: () => false,
|
|
121
|
+
launcher: {},
|
|
122
|
+
discoveryRoots: [dir],
|
|
123
|
+
discoveryOptions: { timeBudgetMs: 2000 },
|
|
124
|
+
})
|
|
125
|
+
assert.equal(found.source, 'discovered')
|
|
126
|
+
assert.ok(found.path.endsWith('xray.exe'))
|
|
127
|
+
} finally {
|
|
128
|
+
process.env.PATH = previousPath
|
|
129
|
+
rmSync(dir, { recursive: true, force: true })
|
|
130
|
+
}
|
|
131
|
+
})
|