@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 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.64",
2
+ "version": "0.1.66",
3
3
  "binaries": {
4
- "linux-x86_64": "cd46ed189a1cb56a4d2ff84af7cda6a4870ad202f6172500e02a29654cb82b54",
5
- "linux-x86_64-glibc2.17": "d578c70bafe98de520b25e78ea10919a12cb14fff0524ab3339bb0d912a78961",
6
- "linux-aarch64": "8acbcce5bd84e14652f778ab987abe2a9f461b3ba4bb0dbe4022f8900a42868e",
7
- "macos-x86_64": "5e96d897d75dc02a0e8acca05bfb8a5910751d38b10de0eda9f1c0ae9ac6df13",
8
- "macos-aarch64": "8481807cd40cac885908550959e259164eaf9d2a20f10e2972c57a5f3e125815",
9
- "windows-x86_64.exe": "90a2a090f993bc6258ccddacf62aa05d99d96b411a39a041cf2e6f19c6f8fd69",
10
- "windows-aarch64.exe": "e96d0dcbfde313747a62cb7b5f945cfdf444927f40e758f25ed06da6c0d517b6"
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
- { headers: { 'user-agent': `${PKG.name}/${PKG.version}` } },
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(await httpGetText(attachmentsUrl), attachmentsUrl);
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
- { headers: { 'user-agent': `${PKG.name}/${PKG.version}` } },
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.64",
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
  },