@tedbrain/vlink 0.1.63 → 0.1.65
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 +37 -0
- package/bin/vlink.js +35 -2
- package/binaries.json +8 -8
- package/install.js +181 -17
- package/installer.js +78 -8
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -37,6 +37,29 @@ npx --registry=https://registry.npmmirror.com -y @tedbrain/vlink@latest --versio
|
|
|
37
37
|
|
|
38
38
|
安装命令会输出安装目标、权限回退原因及 setup 状态;失败时输出错误并返回非零退出码。
|
|
39
39
|
|
|
40
|
+
需要排查安装过程时,加上 `--verbose`:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx --registry=https://registry.npmmirror.com -y \
|
|
44
|
+
@tedbrain/vlink@latest install --verbose
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
verbose 会从 npm 包的 JavaScript wrapper 开始输出 Node、包版本、平台选择、PATH
|
|
48
|
+
查找、权限检查、二进制缓存或下载、复制、补全和 setup 各阶段。也可以设置
|
|
49
|
+
`VLINK_VERBOSE=1`。日志不会打印完整环境变量、registry 凭据或下载 URL 的查询参数。
|
|
50
|
+
|
|
51
|
+
如果上面的命令连 `[TedBrain:verbose] wrapper entered` 都没有输出,使用 npm 的显式
|
|
52
|
+
package 形式,绕过 npx 的命令推断:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm exec --registry=https://registry.npmmirror.com \
|
|
56
|
+
--package=@tedbrain/vlink@latest -- \
|
|
57
|
+
vlink install --verbose
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
若显式形式可以输出,问题发生在本机 npx 的参数路由或缓存阶段;若仍无 wrapper
|
|
61
|
+
输出,可再加 npm 自身的 `--loglevel verbose` 检查 npm 在执行包之前的行为。
|
|
62
|
+
|
|
40
63
|
手动配置 Agent 时可以只安装或更新 vlink,并跳过 TedBrain 配置向导:
|
|
41
64
|
|
|
42
65
|
```bash
|
|
@@ -53,6 +76,20 @@ release 不存在或校验失败时,自动回退到 d01 下载服务器:
|
|
|
53
76
|
回退: http://49.232.144.199/downloads/oss/vlink/<version>/vlink-<platform>
|
|
54
77
|
```
|
|
55
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
|
+
|
|
56
93
|
可用环境变量覆盖:
|
|
57
94
|
|
|
58
95
|
```bash
|
package/bin/vlink.js
CHANGED
|
@@ -2,12 +2,45 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const { spawn } = require('child_process');
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
|
|
6
|
+
function verboseEnabled(args, env = process.env) {
|
|
7
|
+
const value = String(env.VLINK_VERBOSE || '').toLowerCase();
|
|
8
|
+
return args[0] === 'install' && (
|
|
9
|
+
args.includes('--verbose') || ['1', 'true', 'yes', 'on'].includes(value)
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function verbose(message) {
|
|
14
|
+
process.stderr.write(`[TedBrain:verbose] ${message}\n`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function diagnosticArgs(args) {
|
|
18
|
+
const safe = new Set(['install', '--verbose', '--no-setup', '--help', '-h']);
|
|
19
|
+
return args.map((arg) => {
|
|
20
|
+
if (safe.has(arg)) return arg;
|
|
21
|
+
if (arg.startsWith('--')) return arg.split('=', 1)[0];
|
|
22
|
+
return '<argument>';
|
|
23
|
+
});
|
|
24
|
+
}
|
|
7
25
|
|
|
8
26
|
async function main() {
|
|
9
27
|
const args = process.argv.slice(2);
|
|
28
|
+
const diagnostic = verboseEnabled(args);
|
|
29
|
+
if (diagnostic) {
|
|
30
|
+
process.env.VLINK_VERBOSE = '1';
|
|
31
|
+
verbose('wrapper entered');
|
|
32
|
+
verbose(`node=${process.version} executable=${process.execPath}`);
|
|
33
|
+
verbose(`wrapper=${__filename}`);
|
|
34
|
+
verbose(`cwd=${process.cwd()}`);
|
|
35
|
+
verbose(`argv=${JSON.stringify(diagnosticArgs(args))}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const { ensureBinary, VERSION } = require('../install');
|
|
39
|
+
const { runInstallerFromArgs } = require('../installer');
|
|
40
|
+
if (diagnostic) verbose(`package version=${VERSION}`);
|
|
41
|
+
|
|
10
42
|
if (args[0] === 'install') {
|
|
43
|
+
if (diagnostic) verbose('dispatching install command');
|
|
11
44
|
await runInstallerFromArgs(args.slice(1));
|
|
12
45
|
return;
|
|
13
46
|
}
|
package/binaries.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.1.
|
|
2
|
+
"version": "0.1.65",
|
|
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": "8cfd3a34f9e8ddde2e14a27343fb8db8d3992ef3a0716fd19bf57f2c6ea8ef40",
|
|
5
|
+
"linux-x86_64-glibc2.17": "f74562c2b1c8a05f215d9ac05585fa70434136a77fb2c2525d90f0ebc60e1ed6",
|
|
6
|
+
"linux-aarch64": "bb2f87265642d333e3989218bec30ba2cc608f8bcf691cfd3033247c08c40533",
|
|
7
|
+
"macos-x86_64": "17906134f4d045e1177698589c46dfdd94cec249d51e8da2f7189f1e42d711b9",
|
|
8
|
+
"macos-aarch64": "051b1032ed78b2fe70c84debd2a27c69a7110d5703aee3dcf412e8393f915814",
|
|
9
|
+
"windows-x86_64.exe": "5b774f8ca3cf3416c5dbefa745ba7b652ef3291427c86e01b33175734892b9d2",
|
|
10
|
+
"windows-aarch64.exe": "85fe354aa315ffc1391b32e941dcfa954f67c6a1bc8ffb1ffaa292048e8cf864"
|
|
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'));
|
|
@@ -26,6 +28,132 @@ const GLIBC217_SUFFIX = 'linux-x86_64-glibc2.17';
|
|
|
26
28
|
|
|
27
29
|
class VlinkInstallError extends Error {}
|
|
28
30
|
|
|
31
|
+
function verboseEnabled(options = {}) {
|
|
32
|
+
if (options.verbose === true) return true;
|
|
33
|
+
return ['1', 'true', 'yes', 'on'].includes(
|
|
34
|
+
String(process.env.VLINK_VERBOSE || '').toLowerCase()
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function verboseLog(message, options = {}) {
|
|
39
|
+
if (!verboseEnabled(options)) return;
|
|
40
|
+
(options.output || process.stderr).write(`[vlink:verbose] ${message}\n`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function displayUrl(value) {
|
|
44
|
+
try {
|
|
45
|
+
const url = new URL(value);
|
|
46
|
+
url.username = '';
|
|
47
|
+
url.password = '';
|
|
48
|
+
url.search = '';
|
|
49
|
+
url.hash = '';
|
|
50
|
+
return url.toString();
|
|
51
|
+
} catch {
|
|
52
|
+
return '<invalid-url>';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
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
|
+
|
|
29
157
|
function parseGlibcVersion(text) {
|
|
30
158
|
for (const line of String(text || '').split(/\r?\n/)) {
|
|
31
159
|
const glibcMatch = line.match(/\bglibc\s+([0-9]+(?:\.[0-9]+)+)/);
|
|
@@ -116,13 +244,16 @@ function fallbackDownloadUrl(spec, suffix = spec.suffix) {
|
|
|
116
244
|
return `${DOWNLOAD_BASE}/${CHANNEL}/vlink/${VERSION}/vlink-${suffix}`;
|
|
117
245
|
}
|
|
118
246
|
|
|
119
|
-
function httpGetText(url) {
|
|
247
|
+
function httpGetText(url, options = {}) {
|
|
120
248
|
return new Promise((resolve, reject) => {
|
|
121
249
|
const request = (target, redirects) => {
|
|
122
250
|
const transport = target.startsWith('https:') ? https : http;
|
|
123
251
|
const req = transport.get(
|
|
124
252
|
target,
|
|
125
|
-
{
|
|
253
|
+
{
|
|
254
|
+
agent: requestAgent(target, options),
|
|
255
|
+
headers: { 'user-agent': `${PKG.name}/${PKG.version}` }
|
|
256
|
+
},
|
|
126
257
|
(res) => {
|
|
127
258
|
if (
|
|
128
259
|
res.statusCode >= 300 &&
|
|
@@ -137,7 +268,7 @@ function httpGetText(url) {
|
|
|
137
268
|
if (res.statusCode !== 200) {
|
|
138
269
|
res.resume();
|
|
139
270
|
return reject(
|
|
140
|
-
new VlinkInstallError(`HTTP ${res.statusCode} from ${target}`)
|
|
271
|
+
new VlinkInstallError(`HTTP ${res.statusCode} from ${displayUrl(target)}`)
|
|
141
272
|
);
|
|
142
273
|
}
|
|
143
274
|
|
|
@@ -163,19 +294,22 @@ function parseJson(text, url) {
|
|
|
163
294
|
}
|
|
164
295
|
}
|
|
165
296
|
|
|
166
|
-
async function giteeReleaseId(version) {
|
|
297
|
+
async function giteeReleaseId(version, options = {}) {
|
|
167
298
|
const url = `${GITEE_API_BASE}/releases/tags/${version}`;
|
|
168
|
-
const release = parseJson(await httpGetText(url), url);
|
|
299
|
+
const release = parseJson(await httpGetText(url, options), url);
|
|
169
300
|
if (!release || typeof release !== 'object' || !Number.isInteger(release.id)) {
|
|
170
301
|
throw new VlinkInstallError(`gitee release is missing an id: ${url}`);
|
|
171
302
|
}
|
|
172
303
|
return release.id;
|
|
173
304
|
}
|
|
174
305
|
|
|
175
|
-
async function giteeAttachmentDownloadUrl(spec, suffix = spec.suffix) {
|
|
176
|
-
const releaseId = await giteeReleaseId(VERSION);
|
|
306
|
+
async function giteeAttachmentDownloadUrl(spec, suffix = spec.suffix, options = {}) {
|
|
307
|
+
const releaseId = await giteeReleaseId(VERSION, options);
|
|
177
308
|
const attachmentsUrl = `${GITEE_API_BASE}/releases/${releaseId}/attach_files?per_page=100`;
|
|
178
|
-
const attachments = parseJson(
|
|
309
|
+
const attachments = parseJson(
|
|
310
|
+
await httpGetText(attachmentsUrl, options),
|
|
311
|
+
attachmentsUrl
|
|
312
|
+
);
|
|
179
313
|
if (!Array.isArray(attachments)) {
|
|
180
314
|
throw new VlinkInstallError(`gitee attachments response is not an array: ${attachmentsUrl}`);
|
|
181
315
|
}
|
|
@@ -198,12 +332,12 @@ async function giteeAttachmentDownloadUrl(spec, suffix = spec.suffix) {
|
|
|
198
332
|
throw new VlinkInstallError(`gitee attachment has no usable download URL: ${remoteName}`);
|
|
199
333
|
}
|
|
200
334
|
|
|
201
|
-
function buildDownloadSources(spec, suffix = spec.suffix) {
|
|
335
|
+
function buildDownloadSources(spec, suffix = spec.suffix, options = {}) {
|
|
202
336
|
const sources = [];
|
|
203
337
|
if (process.env.VLINK_SKIP_GITEE !== '1') {
|
|
204
338
|
sources.push({
|
|
205
339
|
name: 'gitee',
|
|
206
|
-
resolve: () => giteeAttachmentDownloadUrl(spec, suffix)
|
|
340
|
+
resolve: () => giteeAttachmentDownloadUrl(spec, suffix, options)
|
|
207
341
|
});
|
|
208
342
|
}
|
|
209
343
|
sources.push({
|
|
@@ -278,7 +412,7 @@ function sha256File(filePath) {
|
|
|
278
412
|
});
|
|
279
413
|
}
|
|
280
414
|
|
|
281
|
-
function downloadFile(url, destPath) {
|
|
415
|
+
function downloadFile(url, destPath, options = {}) {
|
|
282
416
|
return new Promise((resolve, reject) => {
|
|
283
417
|
const fileStream = fs.createWriteStream(destPath, { mode: 0o755 });
|
|
284
418
|
|
|
@@ -293,7 +427,10 @@ function downloadFile(url, destPath) {
|
|
|
293
427
|
const transport = target.startsWith('https:') ? https : http;
|
|
294
428
|
const req = transport.get(
|
|
295
429
|
target,
|
|
296
|
-
{
|
|
430
|
+
{
|
|
431
|
+
agent: requestAgent(target, options),
|
|
432
|
+
headers: { 'user-agent': `${PKG.name}/${PKG.version}` }
|
|
433
|
+
},
|
|
297
434
|
(res) => {
|
|
298
435
|
if (
|
|
299
436
|
res.statusCode >= 300 &&
|
|
@@ -308,7 +445,9 @@ function downloadFile(url, destPath) {
|
|
|
308
445
|
if (res.statusCode !== 200) {
|
|
309
446
|
res.resume();
|
|
310
447
|
return reject(
|
|
311
|
-
new VlinkInstallError(
|
|
448
|
+
new VlinkInstallError(
|
|
449
|
+
`download failed: HTTP ${res.statusCode} from ${displayUrl(target)}`
|
|
450
|
+
)
|
|
312
451
|
);
|
|
313
452
|
}
|
|
314
453
|
res.pipe(fileStream);
|
|
@@ -339,30 +478,46 @@ function verifyBinary(binaryPath) {
|
|
|
339
478
|
}
|
|
340
479
|
}
|
|
341
480
|
|
|
342
|
-
async function ensureBinary(
|
|
481
|
+
async function ensureBinary(options = {}) {
|
|
482
|
+
const force = options.force === true;
|
|
343
483
|
const spec = platformSpec();
|
|
344
484
|
const binaryPath = resolveBinaryPath(spec);
|
|
485
|
+
verboseLog(
|
|
486
|
+
`binary resolver: version=${VERSION} platform=${spec.platform}/${spec.arch} ` +
|
|
487
|
+
`suffixes=${spec.suffixes.join(',')}`,
|
|
488
|
+
options
|
|
489
|
+
);
|
|
490
|
+
verboseLog(`binary resolver: selected path=${binaryPath} force=${force}`, options);
|
|
345
491
|
if (!force && isExecutable(binaryPath)) {
|
|
492
|
+
verboseLog('binary resolver: using existing executable', options);
|
|
346
493
|
return binaryPath;
|
|
347
494
|
}
|
|
348
495
|
|
|
349
496
|
const dir = path.dirname(binaryPath);
|
|
497
|
+
verboseLog(`binary resolver: creating download directory ${dir}`, options);
|
|
350
498
|
fs.mkdirSync(dir, { recursive: true });
|
|
351
499
|
|
|
352
500
|
let lastError = new VlinkInstallError('no download source available');
|
|
353
501
|
|
|
354
502
|
for (const suffix of spec.suffixes) {
|
|
355
503
|
const tempPath = path.join(dir, `.${spec.binaryName}.download-${process.pid}`);
|
|
356
|
-
const sources = buildDownloadSources(spec, suffix);
|
|
504
|
+
const sources = buildDownloadSources(spec, suffix, options);
|
|
505
|
+
verboseLog(
|
|
506
|
+
`download stage: suffix=${suffix} sources=${sources.map((source) => source.name).join(',')}`,
|
|
507
|
+
options
|
|
508
|
+
);
|
|
357
509
|
for (const source of sources) {
|
|
358
510
|
try {
|
|
511
|
+
verboseLog(`download stage: resolving ${source.name}`, options);
|
|
359
512
|
const url = await source.resolve();
|
|
360
|
-
console.error(`[vlink] downloading ${url} [${source.name}]`);
|
|
513
|
+
console.error(`[vlink] downloading ${displayUrl(url)} [${source.name}]`);
|
|
361
514
|
fs.rmSync(tempPath, { force: true });
|
|
362
|
-
await downloadFile(url, tempPath);
|
|
515
|
+
await downloadFile(url, tempPath, options);
|
|
516
|
+
verboseLog(`download stage: received ${source.name} payload`, options);
|
|
363
517
|
|
|
364
518
|
const checksum = expectedChecksum(spec, suffix);
|
|
365
519
|
if (checksum) {
|
|
520
|
+
verboseLog(`verification stage: checking SHA-256 for ${suffix}`, options);
|
|
366
521
|
const actual = await sha256File(tempPath);
|
|
367
522
|
if (actual !== checksum) {
|
|
368
523
|
throw new VlinkInstallError(
|
|
@@ -376,7 +531,9 @@ async function ensureBinary({ force = false } = {}) {
|
|
|
376
531
|
}
|
|
377
532
|
if (fs.existsSync(binaryPath)) fs.rmSync(binaryPath, { force: true });
|
|
378
533
|
fs.renameSync(tempPath, binaryPath);
|
|
534
|
+
verboseLog(`verification stage: executing ${binaryPath} -V`, options);
|
|
379
535
|
verifyBinary(binaryPath);
|
|
536
|
+
verboseLog('verification stage: binary is ready', options);
|
|
380
537
|
return binaryPath;
|
|
381
538
|
} catch (err) {
|
|
382
539
|
lastError = err;
|
|
@@ -393,6 +550,12 @@ module.exports = {
|
|
|
393
550
|
VlinkInstallError,
|
|
394
551
|
VERSION,
|
|
395
552
|
GLIBC217_SUFFIX,
|
|
553
|
+
verboseEnabled,
|
|
554
|
+
displayUrl,
|
|
555
|
+
noProxyMatches,
|
|
556
|
+
proxyForUrl,
|
|
557
|
+
requestAgent,
|
|
558
|
+
httpGetText,
|
|
396
559
|
parseGlibcVersion,
|
|
397
560
|
glibcVersion,
|
|
398
561
|
versionAtLeast,
|
|
@@ -400,6 +563,7 @@ module.exports = {
|
|
|
400
563
|
fallbackDownloadUrl,
|
|
401
564
|
giteeAttachmentDownloadUrl,
|
|
402
565
|
buildDownloadSources,
|
|
566
|
+
downloadFile,
|
|
403
567
|
resolveBinaryPath,
|
|
404
568
|
ensureBinary,
|
|
405
569
|
verifyBinary
|
package/installer.js
CHANGED
|
@@ -18,6 +18,20 @@ function log(message, output = process.stderr) {
|
|
|
18
18
|
output.write(`[TedBrain] ${message}\n`);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
function verboseEnabled(options = {}) {
|
|
22
|
+
if (options.verbose === true) return true;
|
|
23
|
+
const env = options.env || process.env;
|
|
24
|
+
return ['1', 'true', 'yes', 'on'].includes(
|
|
25
|
+
String(env.VLINK_VERBOSE || '').toLowerCase()
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function verboseLog(message, options = {}) {
|
|
30
|
+
if (!verboseEnabled(options)) return;
|
|
31
|
+
const output = options.output || process.stderr;
|
|
32
|
+
output.write(`[TedBrain:verbose] ${message}\n`);
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
function environmentValue(env, name) {
|
|
22
36
|
const expected = name.toLowerCase();
|
|
23
37
|
for (const key of Object.keys(env)) {
|
|
@@ -170,6 +184,7 @@ function ensureUserPath(installDir, options = {}) {
|
|
|
170
184
|
const fsImpl = options.fs || fs;
|
|
171
185
|
const output = options.output || process.stderr;
|
|
172
186
|
|
|
187
|
+
verboseLog(`PATH stage: prioritizing ${installDir}`, options);
|
|
173
188
|
prependEnvironmentPath(installDir, env, platform);
|
|
174
189
|
|
|
175
190
|
if (platform === 'win32') {
|
|
@@ -291,7 +306,11 @@ function installBinary(sourcePath, targetPath, options = {}) {
|
|
|
291
306
|
const spawnSyncImpl = options.spawnSync || spawnSync;
|
|
292
307
|
const output = options.output || process.stderr;
|
|
293
308
|
|
|
294
|
-
if (samePath(sourcePath, targetPath, platform))
|
|
309
|
+
if (samePath(sourcePath, targetPath, platform)) {
|
|
310
|
+
verboseLog(`copy stage: source already equals target ${targetPath}`, options);
|
|
311
|
+
return targetPath;
|
|
312
|
+
}
|
|
313
|
+
verboseLog(`copy stage: ${sourcePath} -> ${targetPath}`, options);
|
|
295
314
|
fsImpl.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
296
315
|
|
|
297
316
|
if (options.stopDaemon) {
|
|
@@ -365,6 +384,7 @@ function installWindowsCommandShim(vlinkPath, options = {}) {
|
|
|
365
384
|
function installShellCompletion(vlinkPath, options = {}) {
|
|
366
385
|
const spawnSyncImpl = options.spawnSync || spawnSync;
|
|
367
386
|
const output = options.output || process.stderr;
|
|
387
|
+
verboseLog(`completion stage: executing ${vlinkPath}`, options);
|
|
368
388
|
const result = spawnSyncImpl(
|
|
369
389
|
vlinkPath,
|
|
370
390
|
['completion', '--install', '--quiet'],
|
|
@@ -400,7 +420,11 @@ async function migrateStaticVlink(vlinkPath, spec, options) {
|
|
|
400
420
|
if (!binaryIsStaticElf(vlinkPath, options)) return vlinkPath;
|
|
401
421
|
|
|
402
422
|
log('migrating the installed static vlink to the glibc 2.17 build', options.output);
|
|
403
|
-
const sourcePath = await options.ensureBinary({
|
|
423
|
+
const sourcePath = await options.ensureBinary({
|
|
424
|
+
force: true,
|
|
425
|
+
verbose: verboseEnabled(options),
|
|
426
|
+
output: options.output
|
|
427
|
+
});
|
|
404
428
|
return installBinary(sourcePath, vlinkPath, {
|
|
405
429
|
...options,
|
|
406
430
|
stopDaemon: true
|
|
@@ -412,6 +436,7 @@ async function runInstaller(options = {}) {
|
|
|
412
436
|
const platform = options.platform || env.VLINK_PLATFORM || process.platform;
|
|
413
437
|
const arch = options.arch || env.VLINK_ARCH || process.arch;
|
|
414
438
|
const skipSetup = Boolean(options.skipSetup);
|
|
439
|
+
const verbose = verboseEnabled(options);
|
|
415
440
|
const home = options.home || os.homedir();
|
|
416
441
|
const fsImpl = options.fs || fs;
|
|
417
442
|
const output = options.output || process.stderr;
|
|
@@ -420,6 +445,17 @@ async function runInstaller(options = {}) {
|
|
|
420
445
|
const ensureBinary =
|
|
421
446
|
options.ensureBinary || ((settings) => installModule.ensureBinary(settings));
|
|
422
447
|
|
|
448
|
+
verboseLog(
|
|
449
|
+
`installer entered: package=${installModule.VERSION} platform=${platform} arch=${arch}`,
|
|
450
|
+
{ ...options, env, output, verbose }
|
|
451
|
+
);
|
|
452
|
+
verboseLog(`runtime: node=${process.version} executable=${process.execPath}`, {
|
|
453
|
+
...options,
|
|
454
|
+
env,
|
|
455
|
+
output,
|
|
456
|
+
verbose
|
|
457
|
+
});
|
|
458
|
+
|
|
423
459
|
const spec = platformSpec({
|
|
424
460
|
platform,
|
|
425
461
|
arch,
|
|
@@ -437,14 +473,32 @@ async function runInstaller(options = {}) {
|
|
|
437
473
|
home,
|
|
438
474
|
fs: fsImpl,
|
|
439
475
|
output,
|
|
440
|
-
spawnSync: spawnSyncImpl
|
|
476
|
+
spawnSync: spawnSyncImpl,
|
|
477
|
+
verbose
|
|
441
478
|
};
|
|
442
479
|
|
|
480
|
+
verboseLog(
|
|
481
|
+
`platform stage: suffixes=${spec.suffixes.join(',')} binary=${spec.binaryName}`,
|
|
482
|
+
sharedOptions
|
|
483
|
+
);
|
|
484
|
+
verboseLog(
|
|
485
|
+
`target stage: installDir=${installDir} configured=${Boolean(env.VLINK_INSTALL_DIR)}`,
|
|
486
|
+
sharedOptions
|
|
487
|
+
);
|
|
488
|
+
const pathEntries = String(environmentValue(env, 'PATH'))
|
|
489
|
+
.split(platform === 'win32' ? ';' : path.delimiter)
|
|
490
|
+
.filter(Boolean).length;
|
|
491
|
+
verboseLog(`PATH stage: inspecting ${pathEntries} entries`, sharedOptions);
|
|
492
|
+
|
|
443
493
|
let vlinkPath = findOnPath(spec.binaryName, {
|
|
444
494
|
env,
|
|
445
495
|
platform,
|
|
446
496
|
fs: fsImpl
|
|
447
497
|
});
|
|
498
|
+
verboseLog(
|
|
499
|
+
`discovery stage: native PATH binary=${vlinkPath || 'none'}`,
|
|
500
|
+
sharedOptions
|
|
501
|
+
);
|
|
448
502
|
|
|
449
503
|
if (vlinkPath) {
|
|
450
504
|
log(
|
|
@@ -465,9 +519,11 @@ async function runInstaller(options = {}) {
|
|
|
465
519
|
let installTarget = env.VLINK_INSTALL_DIR ? targetPath : (vlinkPath || targetPath);
|
|
466
520
|
// Avoid trying to replace a shared installation, even if its parent is writable.
|
|
467
521
|
if (vlinkPath && !env.VLINK_INSTALL_DIR && !samePath(vlinkPath, targetPath, platform)) {
|
|
522
|
+
verboseLog(`permission stage: checking ${vlinkPath} and its parent directory`, sharedOptions);
|
|
468
523
|
try {
|
|
469
524
|
fsImpl.accessSync(vlinkPath, fs.constants.W_OK);
|
|
470
525
|
fsImpl.accessSync(path.dirname(vlinkPath), fs.constants.W_OK);
|
|
526
|
+
verboseLog('permission stage: existing installation is replaceable', sharedOptions);
|
|
471
527
|
} catch (error) {
|
|
472
528
|
if (!PERMISSION_ERRORS.has(error.code)) throw error;
|
|
473
529
|
log(`cannot replace ${vlinkPath} (${error.code}); falling back to ${targetPath}`, output);
|
|
@@ -476,7 +532,9 @@ async function runInstaller(options = {}) {
|
|
|
476
532
|
}
|
|
477
533
|
installTarget = path.resolve(installTarget);
|
|
478
534
|
log(`installation target: ${installTarget}`, output);
|
|
479
|
-
|
|
535
|
+
verboseLog('binary stage: resolving packaged or cached binary', sharedOptions);
|
|
536
|
+
const sourcePath = await ensureBinary({ verbose, output });
|
|
537
|
+
verboseLog(`binary stage: source=${sourcePath}`, sharedOptions);
|
|
480
538
|
try {
|
|
481
539
|
installBinary(sourcePath, installTarget, {
|
|
482
540
|
...sharedOptions,
|
|
@@ -500,18 +558,24 @@ async function runInstaller(options = {}) {
|
|
|
500
558
|
ensureBinary
|
|
501
559
|
});
|
|
502
560
|
const commandShimPath = installWindowsCommandShim(vlinkPath, sharedOptions);
|
|
561
|
+
verboseLog(
|
|
562
|
+
`shim stage: ${commandShimPath || 'not required on this platform'}`,
|
|
563
|
+
sharedOptions
|
|
564
|
+
);
|
|
503
565
|
installShellCompletion(vlinkPath, sharedOptions);
|
|
504
566
|
|
|
505
567
|
if (skipSetup) {
|
|
506
568
|
log('skipping TedBrain Agent setup', output);
|
|
507
569
|
} else {
|
|
508
570
|
log('starting TedBrain Agent setup', output);
|
|
571
|
+
verboseLog(`setup stage: executing ${vlinkPath} setup tedbrain`, sharedOptions);
|
|
509
572
|
const setup = spawnSyncImpl(vlinkPath, ['setup', 'tedbrain'], {
|
|
510
573
|
env,
|
|
511
574
|
stdio: 'inherit'
|
|
512
575
|
});
|
|
513
576
|
const failure = commandFailure(setup, 'vlink setup tedbrain');
|
|
514
577
|
if (failure) throw new VlinkSetupError(failure);
|
|
578
|
+
verboseLog('setup stage: completed successfully', sharedOptions);
|
|
515
579
|
}
|
|
516
580
|
|
|
517
581
|
log(`installation complete: ${vlinkPath}`, output);
|
|
@@ -522,11 +586,12 @@ async function runInstaller(options = {}) {
|
|
|
522
586
|
function printInstallHelp(output = process.stdout) {
|
|
523
587
|
output.write(
|
|
524
588
|
[
|
|
525
|
-
'Usage: npx --registry=https://registry.npmmirror.com -y @tedbrain/vlink@latest install [--no-setup]',
|
|
589
|
+
'Usage: npx --registry=https://registry.npmmirror.com -y @tedbrain/vlink@latest install [--no-setup] [--verbose]',
|
|
526
590
|
'',
|
|
527
591
|
'Installs or updates vlink and adds it to the user PATH.',
|
|
528
592
|
'',
|
|
529
|
-
' --no-setup Install without starting the TedBrain setup wizard.'
|
|
593
|
+
' --no-setup Install without starting the TedBrain setup wizard.',
|
|
594
|
+
' --verbose Print diagnostics for every installation stage.'
|
|
530
595
|
].join('\n') + '\n'
|
|
531
596
|
);
|
|
532
597
|
}
|
|
@@ -538,18 +603,23 @@ async function runInstallerFromArgs(args, options = {}) {
|
|
|
538
603
|
}
|
|
539
604
|
|
|
540
605
|
let skipSetup = false;
|
|
606
|
+
let verbose = verboseEnabled(options);
|
|
541
607
|
for (const arg of args) {
|
|
542
608
|
if (arg === '--no-setup') {
|
|
543
609
|
skipSetup = true;
|
|
610
|
+
} else if (arg === '--verbose') {
|
|
611
|
+
verbose = true;
|
|
544
612
|
} else {
|
|
545
|
-
|
|
613
|
+
const displayArg = arg.startsWith('--') ? arg.split('=', 1)[0] : '<argument>';
|
|
614
|
+
throw new VlinkSetupError(`unknown install option: ${displayArg}`);
|
|
546
615
|
}
|
|
547
616
|
}
|
|
548
|
-
return runInstaller({ ...options, skipSetup });
|
|
617
|
+
return runInstaller({ ...options, skipSetup, verbose });
|
|
549
618
|
}
|
|
550
619
|
|
|
551
620
|
module.exports = {
|
|
552
621
|
VlinkSetupError,
|
|
622
|
+
verboseEnabled,
|
|
553
623
|
defaultInstallDir,
|
|
554
624
|
installPathFor,
|
|
555
625
|
isExecutableFile,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tedbrain/vlink",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.65",
|
|
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
|
},
|