@tedbrain/vlink 0.1.64 → 0.1.66
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 +14 -0
- package/binaries.json +8 -8
- package/install.js +130 -13
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -76,6 +76,20 @@ release 不存在或校验失败时,自动回退到 d01 下载服务器:
|
|
|
76
76
|
回退: http://49.232.144.199/downloads/oss/vlink/<version>/vlink-<platform>
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
下载器遵循标准代理环境变量,同时支持大写和小写形式:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
HTTP_PROXY=http://proxy.example:8080
|
|
83
|
+
HTTPS_PROXY=http://proxy.example:8080
|
|
84
|
+
NO_PROXY=localhost,127.0.0.1,.internal.example
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
也支持 `http_proxy`、`https_proxy`、`no_proxy`、`ALL_PROXY`,以及 npm 注入的
|
|
88
|
+
`npm_config_proxy`、`npm_config_https_proxy` 和 `npm_config_noproxy`。小写变量与
|
|
89
|
+
大写变量同时存在时优先使用小写。HTTPS 请求通过代理的 CONNECT 隧道传输;每次
|
|
90
|
+
重定向都会根据目标地址重新应用代理和 `NO_PROXY`。verbose 日志仅显示脱敏后的
|
|
91
|
+
代理主机,不显示代理用户名、密码或下载 URL 查询参数。
|
|
92
|
+
|
|
79
93
|
可用环境变量覆盖:
|
|
80
94
|
|
|
81
95
|
```bash
|
package/binaries.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.1.
|
|
2
|
+
"version": "0.1.66",
|
|
3
3
|
"binaries": {
|
|
4
|
-
"linux-x86_64": "
|
|
5
|
-
"linux-x86_64-glibc2.17": "
|
|
6
|
-
"linux-aarch64": "
|
|
7
|
-
"macos-x86_64": "
|
|
8
|
-
"macos-aarch64": "
|
|
9
|
-
"windows-x86_64.exe": "
|
|
10
|
-
"windows-aarch64.exe": "
|
|
4
|
+
"linux-x86_64": "c2c49f8b91add274f11157ba7b3d1f9d1c6bad2773163e0c5519f5ffa2e264d8",
|
|
5
|
+
"linux-x86_64-glibc2.17": "5fcd9b7e0263d566c92799524a2ca4bccb181687c5ad11e5cd3ef6a625e5fc26",
|
|
6
|
+
"linux-aarch64": "ef1568f34062697a88c91837ead906178dd8a9a64bc63140765c57782db82ca9",
|
|
7
|
+
"macos-x86_64": "d33fb87786b1d61e386fd4b3452580cfea1faae6aa511e61027bfca7ca9e4e9d",
|
|
8
|
+
"macos-aarch64": "59e577015da3f14fb2cc1010bd1cf66a59d9510d068174fe3e86dd6cbccbb187",
|
|
9
|
+
"windows-x86_64.exe": "a5a9e65fc50e035e335530abd211e0341e9be539464e01f2fc95ce4dea502955",
|
|
10
|
+
"windows-aarch64.exe": "f0ecc9bd1211ebaf9be04ade0d8c9722c024c841483b1897af13d2d74f6e52cd"
|
|
11
11
|
}
|
|
12
12
|
}
|
package/install.js
CHANGED
|
@@ -8,6 +8,8 @@ const https = require('https');
|
|
|
8
8
|
const os = require('os');
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const { spawnSync } = require('child_process');
|
|
11
|
+
const { HttpProxyAgent } = require('http-proxy-agent');
|
|
12
|
+
const { HttpsProxyAgent } = require('https-proxy-agent');
|
|
11
13
|
|
|
12
14
|
const PKG_ROOT = path.resolve(__dirname);
|
|
13
15
|
const PKG = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8'));
|
|
@@ -51,6 +53,107 @@ function displayUrl(value) {
|
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
|
|
56
|
+
function firstEnvironmentValue(env, names) {
|
|
57
|
+
for (const name of names) {
|
|
58
|
+
if (Object.prototype.hasOwnProperty.call(env, name) && env[name]) {
|
|
59
|
+
return String(env[name]).trim();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return '';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizeProxyUrl(value) {
|
|
66
|
+
if (!value) return '';
|
|
67
|
+
const candidate = /^[a-z][a-z0-9+.-]*:\/\//i.test(value)
|
|
68
|
+
? value
|
|
69
|
+
: `http://${value}`;
|
|
70
|
+
try {
|
|
71
|
+
return new URL(candidate).toString();
|
|
72
|
+
} catch (error) {
|
|
73
|
+
throw new VlinkInstallError(`invalid proxy URL: ${error.message}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function noProxyMatches(target, value) {
|
|
78
|
+
if (!value) return false;
|
|
79
|
+
const url = new URL(target);
|
|
80
|
+
const hostname = url.hostname.toLowerCase().replace(/^\[|\]$/g, '');
|
|
81
|
+
const port = url.port || (url.protocol === 'https:' ? '443' : '80');
|
|
82
|
+
|
|
83
|
+
return String(value).split(/[\s,]+/).filter(Boolean).some((entry) => {
|
|
84
|
+
if (entry === '*') return true;
|
|
85
|
+
let rule = entry.toLowerCase();
|
|
86
|
+
let rulePort = '';
|
|
87
|
+
if (rule.startsWith('[')) {
|
|
88
|
+
const end = rule.indexOf(']');
|
|
89
|
+
if (end !== -1 && rule[end + 1] === ':') {
|
|
90
|
+
rulePort = rule.slice(end + 2);
|
|
91
|
+
rule = rule.slice(1, end);
|
|
92
|
+
} else if (end !== -1) {
|
|
93
|
+
rule = rule.slice(1, end);
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
const colon = rule.lastIndexOf(':');
|
|
97
|
+
if (colon > -1 && rule.indexOf(':') === colon && /^\d+$/.test(rule.slice(colon + 1))) {
|
|
98
|
+
rulePort = rule.slice(colon + 1);
|
|
99
|
+
rule = rule.slice(0, colon);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (rulePort && rulePort !== port) return false;
|
|
103
|
+
if (rule.startsWith('*')) rule = rule.slice(1);
|
|
104
|
+
if (rule.startsWith('.')) {
|
|
105
|
+
return hostname.endsWith(rule) || hostname === rule.slice(1);
|
|
106
|
+
}
|
|
107
|
+
return hostname === rule || hostname.endsWith(`.${rule}`);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function proxyForUrl(target, env = process.env) {
|
|
112
|
+
const noProxy = firstEnvironmentValue(env, [
|
|
113
|
+
'no_proxy',
|
|
114
|
+
'NO_PROXY',
|
|
115
|
+
'npm_config_noproxy',
|
|
116
|
+
'npm_config_no_proxy'
|
|
117
|
+
]);
|
|
118
|
+
if (noProxyMatches(target, noProxy)) return '';
|
|
119
|
+
|
|
120
|
+
const protocol = new URL(target).protocol;
|
|
121
|
+
const names = protocol === 'https:'
|
|
122
|
+
? [
|
|
123
|
+
'https_proxy',
|
|
124
|
+
'HTTPS_PROXY',
|
|
125
|
+
'npm_config_https_proxy',
|
|
126
|
+
'npm_config_proxy',
|
|
127
|
+
'http_proxy',
|
|
128
|
+
'HTTP_PROXY',
|
|
129
|
+
'all_proxy',
|
|
130
|
+
'ALL_PROXY'
|
|
131
|
+
]
|
|
132
|
+
: [
|
|
133
|
+
'http_proxy',
|
|
134
|
+
'HTTP_PROXY',
|
|
135
|
+
'npm_config_proxy',
|
|
136
|
+
'all_proxy',
|
|
137
|
+
'ALL_PROXY'
|
|
138
|
+
];
|
|
139
|
+
return normalizeProxyUrl(firstEnvironmentValue(env, names));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function requestAgent(target, options = {}) {
|
|
143
|
+
const proxy = proxyForUrl(target, options.env || process.env);
|
|
144
|
+
if (!proxy) {
|
|
145
|
+
verboseLog(`network stage: direct ${displayUrl(target)}`, options);
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
verboseLog(
|
|
149
|
+
`network stage: ${displayUrl(target)} via proxy ${displayUrl(proxy)}`,
|
|
150
|
+
options
|
|
151
|
+
);
|
|
152
|
+
return target.startsWith('https:')
|
|
153
|
+
? new HttpsProxyAgent(proxy)
|
|
154
|
+
: new HttpProxyAgent(proxy);
|
|
155
|
+
}
|
|
156
|
+
|
|
54
157
|
function parseGlibcVersion(text) {
|
|
55
158
|
for (const line of String(text || '').split(/\r?\n/)) {
|
|
56
159
|
const glibcMatch = line.match(/\bglibc\s+([0-9]+(?:\.[0-9]+)+)/);
|
|
@@ -141,13 +244,16 @@ function fallbackDownloadUrl(spec, suffix = spec.suffix) {
|
|
|
141
244
|
return `${DOWNLOAD_BASE}/${CHANNEL}/vlink/${VERSION}/vlink-${suffix}`;
|
|
142
245
|
}
|
|
143
246
|
|
|
144
|
-
function httpGetText(url) {
|
|
247
|
+
function httpGetText(url, options = {}) {
|
|
145
248
|
return new Promise((resolve, reject) => {
|
|
146
249
|
const request = (target, redirects) => {
|
|
147
250
|
const transport = target.startsWith('https:') ? https : http;
|
|
148
251
|
const req = transport.get(
|
|
149
252
|
target,
|
|
150
|
-
{
|
|
253
|
+
{
|
|
254
|
+
agent: requestAgent(target, options),
|
|
255
|
+
headers: { 'user-agent': `${PKG.name}/${PKG.version}` }
|
|
256
|
+
},
|
|
151
257
|
(res) => {
|
|
152
258
|
if (
|
|
153
259
|
res.statusCode >= 300 &&
|
|
@@ -188,19 +294,22 @@ function parseJson(text, url) {
|
|
|
188
294
|
}
|
|
189
295
|
}
|
|
190
296
|
|
|
191
|
-
async function giteeReleaseId(version) {
|
|
297
|
+
async function giteeReleaseId(version, options = {}) {
|
|
192
298
|
const url = `${GITEE_API_BASE}/releases/tags/${version}`;
|
|
193
|
-
const release = parseJson(await httpGetText(url), url);
|
|
299
|
+
const release = parseJson(await httpGetText(url, options), url);
|
|
194
300
|
if (!release || typeof release !== 'object' || !Number.isInteger(release.id)) {
|
|
195
301
|
throw new VlinkInstallError(`gitee release is missing an id: ${url}`);
|
|
196
302
|
}
|
|
197
303
|
return release.id;
|
|
198
304
|
}
|
|
199
305
|
|
|
200
|
-
async function giteeAttachmentDownloadUrl(spec, suffix = spec.suffix) {
|
|
201
|
-
const releaseId = await giteeReleaseId(VERSION);
|
|
306
|
+
async function giteeAttachmentDownloadUrl(spec, suffix = spec.suffix, options = {}) {
|
|
307
|
+
const releaseId = await giteeReleaseId(VERSION, options);
|
|
202
308
|
const attachmentsUrl = `${GITEE_API_BASE}/releases/${releaseId}/attach_files?per_page=100`;
|
|
203
|
-
const attachments = parseJson(
|
|
309
|
+
const attachments = parseJson(
|
|
310
|
+
await httpGetText(attachmentsUrl, options),
|
|
311
|
+
attachmentsUrl
|
|
312
|
+
);
|
|
204
313
|
if (!Array.isArray(attachments)) {
|
|
205
314
|
throw new VlinkInstallError(`gitee attachments response is not an array: ${attachmentsUrl}`);
|
|
206
315
|
}
|
|
@@ -223,12 +332,12 @@ async function giteeAttachmentDownloadUrl(spec, suffix = spec.suffix) {
|
|
|
223
332
|
throw new VlinkInstallError(`gitee attachment has no usable download URL: ${remoteName}`);
|
|
224
333
|
}
|
|
225
334
|
|
|
226
|
-
function buildDownloadSources(spec, suffix = spec.suffix) {
|
|
335
|
+
function buildDownloadSources(spec, suffix = spec.suffix, options = {}) {
|
|
227
336
|
const sources = [];
|
|
228
337
|
if (process.env.VLINK_SKIP_GITEE !== '1') {
|
|
229
338
|
sources.push({
|
|
230
339
|
name: 'gitee',
|
|
231
|
-
resolve: () => giteeAttachmentDownloadUrl(spec, suffix)
|
|
340
|
+
resolve: () => giteeAttachmentDownloadUrl(spec, suffix, options)
|
|
232
341
|
});
|
|
233
342
|
}
|
|
234
343
|
sources.push({
|
|
@@ -303,7 +412,7 @@ function sha256File(filePath) {
|
|
|
303
412
|
});
|
|
304
413
|
}
|
|
305
414
|
|
|
306
|
-
function downloadFile(url, destPath) {
|
|
415
|
+
function downloadFile(url, destPath, options = {}) {
|
|
307
416
|
return new Promise((resolve, reject) => {
|
|
308
417
|
const fileStream = fs.createWriteStream(destPath, { mode: 0o755 });
|
|
309
418
|
|
|
@@ -318,7 +427,10 @@ function downloadFile(url, destPath) {
|
|
|
318
427
|
const transport = target.startsWith('https:') ? https : http;
|
|
319
428
|
const req = transport.get(
|
|
320
429
|
target,
|
|
321
|
-
{
|
|
430
|
+
{
|
|
431
|
+
agent: requestAgent(target, options),
|
|
432
|
+
headers: { 'user-agent': `${PKG.name}/${PKG.version}` }
|
|
433
|
+
},
|
|
322
434
|
(res) => {
|
|
323
435
|
if (
|
|
324
436
|
res.statusCode >= 300 &&
|
|
@@ -389,7 +501,7 @@ async function ensureBinary(options = {}) {
|
|
|
389
501
|
|
|
390
502
|
for (const suffix of spec.suffixes) {
|
|
391
503
|
const tempPath = path.join(dir, `.${spec.binaryName}.download-${process.pid}`);
|
|
392
|
-
const sources = buildDownloadSources(spec, suffix);
|
|
504
|
+
const sources = buildDownloadSources(spec, suffix, options);
|
|
393
505
|
verboseLog(
|
|
394
506
|
`download stage: suffix=${suffix} sources=${sources.map((source) => source.name).join(',')}`,
|
|
395
507
|
options
|
|
@@ -400,7 +512,7 @@ async function ensureBinary(options = {}) {
|
|
|
400
512
|
const url = await source.resolve();
|
|
401
513
|
console.error(`[vlink] downloading ${displayUrl(url)} [${source.name}]`);
|
|
402
514
|
fs.rmSync(tempPath, { force: true });
|
|
403
|
-
await downloadFile(url, tempPath);
|
|
515
|
+
await downloadFile(url, tempPath, options);
|
|
404
516
|
verboseLog(`download stage: received ${source.name} payload`, options);
|
|
405
517
|
|
|
406
518
|
const checksum = expectedChecksum(spec, suffix);
|
|
@@ -440,6 +552,10 @@ module.exports = {
|
|
|
440
552
|
GLIBC217_SUFFIX,
|
|
441
553
|
verboseEnabled,
|
|
442
554
|
displayUrl,
|
|
555
|
+
noProxyMatches,
|
|
556
|
+
proxyForUrl,
|
|
557
|
+
requestAgent,
|
|
558
|
+
httpGetText,
|
|
443
559
|
parseGlibcVersion,
|
|
444
560
|
glibcVersion,
|
|
445
561
|
versionAtLeast,
|
|
@@ -447,6 +563,7 @@ module.exports = {
|
|
|
447
563
|
fallbackDownloadUrl,
|
|
448
564
|
giteeAttachmentDownloadUrl,
|
|
449
565
|
buildDownloadSources,
|
|
566
|
+
downloadFile,
|
|
450
567
|
resolveBinaryPath,
|
|
451
568
|
ensureBinary,
|
|
452
569
|
verifyBinary
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tedbrain/vlink",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.66",
|
|
4
4
|
"description": "vlink Virtuoso bridge CLI installer. Installs the matching platform binary at npm install time, like Claude Code / Codex.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"bin": {
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
"postinstall": "node install.js",
|
|
18
18
|
"test": "node --test test/*.test.js"
|
|
19
19
|
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"http-proxy-agent": "^7.0.2",
|
|
22
|
+
"https-proxy-agent": "^7.0.6"
|
|
23
|
+
},
|
|
20
24
|
"engines": {
|
|
21
25
|
"node": ">=16"
|
|
22
26
|
},
|