@tedbrain/vlink 0.1.62 → 0.1.64
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 +33 -1
- package/bin/vlink.js +35 -2
- package/binaries.json +8 -8
- package/install.js +51 -4
- package/installer.js +122 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,14 @@ npx --registry=https://registry.npmmirror.com -y @tedbrain/vlink@latest install
|
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
该命令会识别平台和架构,始终安装与当前 npm 包版本一致的 vlink,配置用户
|
|
12
|
-
PATH,并启动 `vlink setup tedbrain
|
|
12
|
+
PATH,并启动 `vlink setup tedbrain`。如果 PATH 中已有原生 vlink,会优先更新该位置;
|
|
13
|
+
旧文件或目录不可写时,自动回退到用户安装目录(Unix 为 `~/.local/bin`,Windows 为
|
|
14
|
+
`%LOCALAPPDATA%\TedBrain\bin`),保留原来的共享安装。显式设置 `VLINK_INSTALL_DIR`
|
|
15
|
+
时始终使用该目录,写入失败会报错,不会再改装到其他位置。
|
|
16
|
+
|
|
17
|
+
安装目录会优先加入当前安装进程的 PATH,补全和 setup 均使用新二进制的绝对路径。
|
|
18
|
+
默认目录还会写入 shell 配置或 Windows 用户 PATH;自定义 Unix 目录需要自行配置
|
|
19
|
+
永久 PATH。完成后请打开新终端,让 PATH 和 shell 命令缓存更新,再运行:
|
|
13
20
|
|
|
14
21
|
```bash
|
|
15
22
|
vlink --version
|
|
@@ -28,6 +35,31 @@ Windows 会在 `vlink.exe` 旁同时生成 `vlink.cmd`。安装目录加入用
|
|
|
28
35
|
npx --registry=https://registry.npmmirror.com -y @tedbrain/vlink@latest --version
|
|
29
36
|
```
|
|
30
37
|
|
|
38
|
+
安装命令会输出安装目标、权限回退原因及 setup 状态;失败时输出错误并返回非零退出码。
|
|
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
|
+
|
|
31
63
|
手动配置 Agent 时可以只安装或更新 vlink,并跳过 TedBrain 配置向导:
|
|
32
64
|
|
|
33
65
|
```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.64",
|
|
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": "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"
|
|
11
11
|
}
|
|
12
12
|
}
|
package/install.js
CHANGED
|
@@ -26,6 +26,31 @@ const GLIBC217_SUFFIX = 'linux-x86_64-glibc2.17';
|
|
|
26
26
|
|
|
27
27
|
class VlinkInstallError extends Error {}
|
|
28
28
|
|
|
29
|
+
function verboseEnabled(options = {}) {
|
|
30
|
+
if (options.verbose === true) return true;
|
|
31
|
+
return ['1', 'true', 'yes', 'on'].includes(
|
|
32
|
+
String(process.env.VLINK_VERBOSE || '').toLowerCase()
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function verboseLog(message, options = {}) {
|
|
37
|
+
if (!verboseEnabled(options)) return;
|
|
38
|
+
(options.output || process.stderr).write(`[vlink:verbose] ${message}\n`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function displayUrl(value) {
|
|
42
|
+
try {
|
|
43
|
+
const url = new URL(value);
|
|
44
|
+
url.username = '';
|
|
45
|
+
url.password = '';
|
|
46
|
+
url.search = '';
|
|
47
|
+
url.hash = '';
|
|
48
|
+
return url.toString();
|
|
49
|
+
} catch {
|
|
50
|
+
return '<invalid-url>';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
29
54
|
function parseGlibcVersion(text) {
|
|
30
55
|
for (const line of String(text || '').split(/\r?\n/)) {
|
|
31
56
|
const glibcMatch = line.match(/\bglibc\s+([0-9]+(?:\.[0-9]+)+)/);
|
|
@@ -137,7 +162,7 @@ function httpGetText(url) {
|
|
|
137
162
|
if (res.statusCode !== 200) {
|
|
138
163
|
res.resume();
|
|
139
164
|
return reject(
|
|
140
|
-
new VlinkInstallError(`HTTP ${res.statusCode} from ${target}`)
|
|
165
|
+
new VlinkInstallError(`HTTP ${res.statusCode} from ${displayUrl(target)}`)
|
|
141
166
|
);
|
|
142
167
|
}
|
|
143
168
|
|
|
@@ -308,7 +333,9 @@ function downloadFile(url, destPath) {
|
|
|
308
333
|
if (res.statusCode !== 200) {
|
|
309
334
|
res.resume();
|
|
310
335
|
return reject(
|
|
311
|
-
new VlinkInstallError(
|
|
336
|
+
new VlinkInstallError(
|
|
337
|
+
`download failed: HTTP ${res.statusCode} from ${displayUrl(target)}`
|
|
338
|
+
)
|
|
312
339
|
);
|
|
313
340
|
}
|
|
314
341
|
res.pipe(fileStream);
|
|
@@ -339,14 +366,23 @@ function verifyBinary(binaryPath) {
|
|
|
339
366
|
}
|
|
340
367
|
}
|
|
341
368
|
|
|
342
|
-
async function ensureBinary(
|
|
369
|
+
async function ensureBinary(options = {}) {
|
|
370
|
+
const force = options.force === true;
|
|
343
371
|
const spec = platformSpec();
|
|
344
372
|
const binaryPath = resolveBinaryPath(spec);
|
|
373
|
+
verboseLog(
|
|
374
|
+
`binary resolver: version=${VERSION} platform=${spec.platform}/${spec.arch} ` +
|
|
375
|
+
`suffixes=${spec.suffixes.join(',')}`,
|
|
376
|
+
options
|
|
377
|
+
);
|
|
378
|
+
verboseLog(`binary resolver: selected path=${binaryPath} force=${force}`, options);
|
|
345
379
|
if (!force && isExecutable(binaryPath)) {
|
|
380
|
+
verboseLog('binary resolver: using existing executable', options);
|
|
346
381
|
return binaryPath;
|
|
347
382
|
}
|
|
348
383
|
|
|
349
384
|
const dir = path.dirname(binaryPath);
|
|
385
|
+
verboseLog(`binary resolver: creating download directory ${dir}`, options);
|
|
350
386
|
fs.mkdirSync(dir, { recursive: true });
|
|
351
387
|
|
|
352
388
|
let lastError = new VlinkInstallError('no download source available');
|
|
@@ -354,15 +390,22 @@ async function ensureBinary({ force = false } = {}) {
|
|
|
354
390
|
for (const suffix of spec.suffixes) {
|
|
355
391
|
const tempPath = path.join(dir, `.${spec.binaryName}.download-${process.pid}`);
|
|
356
392
|
const sources = buildDownloadSources(spec, suffix);
|
|
393
|
+
verboseLog(
|
|
394
|
+
`download stage: suffix=${suffix} sources=${sources.map((source) => source.name).join(',')}`,
|
|
395
|
+
options
|
|
396
|
+
);
|
|
357
397
|
for (const source of sources) {
|
|
358
398
|
try {
|
|
399
|
+
verboseLog(`download stage: resolving ${source.name}`, options);
|
|
359
400
|
const url = await source.resolve();
|
|
360
|
-
console.error(`[vlink] downloading ${url} [${source.name}]`);
|
|
401
|
+
console.error(`[vlink] downloading ${displayUrl(url)} [${source.name}]`);
|
|
361
402
|
fs.rmSync(tempPath, { force: true });
|
|
362
403
|
await downloadFile(url, tempPath);
|
|
404
|
+
verboseLog(`download stage: received ${source.name} payload`, options);
|
|
363
405
|
|
|
364
406
|
const checksum = expectedChecksum(spec, suffix);
|
|
365
407
|
if (checksum) {
|
|
408
|
+
verboseLog(`verification stage: checking SHA-256 for ${suffix}`, options);
|
|
366
409
|
const actual = await sha256File(tempPath);
|
|
367
410
|
if (actual !== checksum) {
|
|
368
411
|
throw new VlinkInstallError(
|
|
@@ -376,7 +419,9 @@ async function ensureBinary({ force = false } = {}) {
|
|
|
376
419
|
}
|
|
377
420
|
if (fs.existsSync(binaryPath)) fs.rmSync(binaryPath, { force: true });
|
|
378
421
|
fs.renameSync(tempPath, binaryPath);
|
|
422
|
+
verboseLog(`verification stage: executing ${binaryPath} -V`, options);
|
|
379
423
|
verifyBinary(binaryPath);
|
|
424
|
+
verboseLog('verification stage: binary is ready', options);
|
|
380
425
|
return binaryPath;
|
|
381
426
|
} catch (err) {
|
|
382
427
|
lastError = err;
|
|
@@ -393,6 +438,8 @@ module.exports = {
|
|
|
393
438
|
VlinkInstallError,
|
|
394
439
|
VERSION,
|
|
395
440
|
GLIBC217_SUFFIX,
|
|
441
|
+
verboseEnabled,
|
|
442
|
+
displayUrl,
|
|
396
443
|
parseGlibcVersion,
|
|
397
444
|
glibcVersion,
|
|
398
445
|
versionAtLeast,
|
package/installer.js
CHANGED
|
@@ -8,6 +8,8 @@ const { spawnSync } = require('child_process');
|
|
|
8
8
|
|
|
9
9
|
const installModule = require('./install');
|
|
10
10
|
|
|
11
|
+
const PERMISSION_ERRORS = new Set(['EACCES', 'EPERM', 'EROFS']);
|
|
12
|
+
|
|
11
13
|
const CURRENT_WRAPPER = path.resolve(__dirname, 'bin', 'vlink.js');
|
|
12
14
|
|
|
13
15
|
class VlinkSetupError extends Error {}
|
|
@@ -16,6 +18,20 @@ function log(message, output = process.stderr) {
|
|
|
16
18
|
output.write(`[TedBrain] ${message}\n`);
|
|
17
19
|
}
|
|
18
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
|
+
|
|
19
35
|
function environmentValue(env, name) {
|
|
20
36
|
const expected = name.toLowerCase();
|
|
21
37
|
for (const key of Object.keys(env)) {
|
|
@@ -110,8 +126,8 @@ function prependEnvironmentPath(directory, env, platform) {
|
|
|
110
126
|
const entries = String(current)
|
|
111
127
|
.split(delimiter)
|
|
112
128
|
.filter(Boolean);
|
|
113
|
-
const
|
|
114
|
-
|
|
129
|
+
const next = [directory, ...entries.filter((entry) => !samePath(entry, directory, platform))]
|
|
130
|
+
.join(delimiter);
|
|
115
131
|
|
|
116
132
|
for (const key of Object.keys(env)) {
|
|
117
133
|
if (key.toLowerCase() === 'path') delete env[key];
|
|
@@ -137,8 +153,9 @@ function updateWindowsUserPath(directory, options) {
|
|
|
137
153
|
"$entry = $env:TEDBRAIN_INSTALL_DIR.TrimEnd('\\')",
|
|
138
154
|
"$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')",
|
|
139
155
|
"$entries = @($userPath -split ';' | Where-Object { $_ })",
|
|
140
|
-
"
|
|
141
|
-
"
|
|
156
|
+
"$remaining = @($entries | Where-Object { $_.TrimEnd('\\') -ine $entry })",
|
|
157
|
+
"$newPath = (@($env:TEDBRAIN_INSTALL_DIR) + $remaining) -join ';'",
|
|
158
|
+
"if ($newPath -cne $userPath) {",
|
|
142
159
|
" [Environment]::SetEnvironmentVariable('Path', $newPath, 'User')",
|
|
143
160
|
" Write-Output 'changed'",
|
|
144
161
|
"}"
|
|
@@ -167,6 +184,7 @@ function ensureUserPath(installDir, options = {}) {
|
|
|
167
184
|
const fsImpl = options.fs || fs;
|
|
168
185
|
const output = options.output || process.stderr;
|
|
169
186
|
|
|
187
|
+
verboseLog(`PATH stage: prioritizing ${installDir}`, options);
|
|
170
188
|
prependEnvironmentPath(installDir, env, platform);
|
|
171
189
|
|
|
172
190
|
if (platform === 'win32') {
|
|
@@ -288,12 +306,16 @@ function installBinary(sourcePath, targetPath, options = {}) {
|
|
|
288
306
|
const spawnSyncImpl = options.spawnSync || spawnSync;
|
|
289
307
|
const output = options.output || process.stderr;
|
|
290
308
|
|
|
291
|
-
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);
|
|
292
314
|
fsImpl.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
293
315
|
|
|
294
316
|
if (options.stopDaemon) {
|
|
295
317
|
log('stopping the existing vlink daemon before replacement', output);
|
|
296
|
-
const stop = spawnSyncImpl(sourcePath, ['daemon', 'stop'], { stdio: 'inherit' });
|
|
318
|
+
const stop = spawnSyncImpl(sourcePath, ['daemon', 'stop'], { stdio: 'inherit', env: options.env });
|
|
297
319
|
const failure = commandFailure(stop, 'vlink daemon stop');
|
|
298
320
|
if (failure) throw new VlinkSetupError(failure);
|
|
299
321
|
}
|
|
@@ -311,7 +333,9 @@ function installBinary(sourcePath, targetPath, options = {}) {
|
|
|
311
333
|
fsImpl.renameSync(temporary, targetPath);
|
|
312
334
|
} catch (error) {
|
|
313
335
|
fsImpl.rmSync(temporary, { force: true });
|
|
314
|
-
|
|
336
|
+
const failure = new VlinkSetupError(`failed to install vlink at ${targetPath}: ${error.message}`);
|
|
337
|
+
failure.code = error.code;
|
|
338
|
+
throw failure;
|
|
315
339
|
}
|
|
316
340
|
fsImpl.rmSync(temporary, { force: true });
|
|
317
341
|
log(`installed vlink at ${targetPath}`, output);
|
|
@@ -360,10 +384,12 @@ function installWindowsCommandShim(vlinkPath, options = {}) {
|
|
|
360
384
|
function installShellCompletion(vlinkPath, options = {}) {
|
|
361
385
|
const spawnSyncImpl = options.spawnSync || spawnSync;
|
|
362
386
|
const output = options.output || process.stderr;
|
|
387
|
+
verboseLog(`completion stage: executing ${vlinkPath}`, options);
|
|
363
388
|
const result = spawnSyncImpl(
|
|
364
389
|
vlinkPath,
|
|
365
390
|
['completion', '--install', '--quiet'],
|
|
366
391
|
{
|
|
392
|
+
env: options.env,
|
|
367
393
|
encoding: 'utf8',
|
|
368
394
|
stdio: ['ignore', 'pipe', 'pipe']
|
|
369
395
|
}
|
|
@@ -394,7 +420,11 @@ async function migrateStaticVlink(vlinkPath, spec, options) {
|
|
|
394
420
|
if (!binaryIsStaticElf(vlinkPath, options)) return vlinkPath;
|
|
395
421
|
|
|
396
422
|
log('migrating the installed static vlink to the glibc 2.17 build', options.output);
|
|
397
|
-
const sourcePath = await options.ensureBinary({
|
|
423
|
+
const sourcePath = await options.ensureBinary({
|
|
424
|
+
force: true,
|
|
425
|
+
verbose: verboseEnabled(options),
|
|
426
|
+
output: options.output
|
|
427
|
+
});
|
|
398
428
|
return installBinary(sourcePath, vlinkPath, {
|
|
399
429
|
...options,
|
|
400
430
|
stopDaemon: true
|
|
@@ -406,6 +436,7 @@ async function runInstaller(options = {}) {
|
|
|
406
436
|
const platform = options.platform || env.VLINK_PLATFORM || process.platform;
|
|
407
437
|
const arch = options.arch || env.VLINK_ARCH || process.arch;
|
|
408
438
|
const skipSetup = Boolean(options.skipSetup);
|
|
439
|
+
const verbose = verboseEnabled(options);
|
|
409
440
|
const home = options.home || os.homedir();
|
|
410
441
|
const fsImpl = options.fs || fs;
|
|
411
442
|
const output = options.output || process.stderr;
|
|
@@ -414,6 +445,17 @@ async function runInstaller(options = {}) {
|
|
|
414
445
|
const ensureBinary =
|
|
415
446
|
options.ensureBinary || ((settings) => installModule.ensureBinary(settings));
|
|
416
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
|
+
|
|
417
459
|
const spec = platformSpec({
|
|
418
460
|
platform,
|
|
419
461
|
arch,
|
|
@@ -423,7 +465,7 @@ async function runInstaller(options = {}) {
|
|
|
423
465
|
: env.VLINK_GLIBC_VERSION,
|
|
424
466
|
spawnSync: spawnSyncImpl
|
|
425
467
|
});
|
|
426
|
-
const installDir = defaultInstallDir(spec, env, home);
|
|
468
|
+
const installDir = path.resolve(defaultInstallDir(spec, env, home));
|
|
427
469
|
const targetPath = installPathFor(spec, installDir);
|
|
428
470
|
const sharedOptions = {
|
|
429
471
|
env,
|
|
@@ -431,14 +473,32 @@ async function runInstaller(options = {}) {
|
|
|
431
473
|
home,
|
|
432
474
|
fs: fsImpl,
|
|
433
475
|
output,
|
|
434
|
-
spawnSync: spawnSyncImpl
|
|
476
|
+
spawnSync: spawnSyncImpl,
|
|
477
|
+
verbose
|
|
435
478
|
};
|
|
436
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
|
+
|
|
437
493
|
let vlinkPath = findOnPath(spec.binaryName, {
|
|
438
494
|
env,
|
|
439
495
|
platform,
|
|
440
496
|
fs: fsImpl
|
|
441
497
|
});
|
|
498
|
+
verboseLog(
|
|
499
|
+
`discovery stage: native PATH binary=${vlinkPath || 'none'}`,
|
|
500
|
+
sharedOptions
|
|
501
|
+
);
|
|
442
502
|
|
|
443
503
|
if (vlinkPath) {
|
|
444
504
|
log(
|
|
@@ -447,7 +507,6 @@ async function runInstaller(options = {}) {
|
|
|
447
507
|
);
|
|
448
508
|
} else if (isExecutableFile(targetPath, platform, fsImpl)) {
|
|
449
509
|
vlinkPath = targetPath;
|
|
450
|
-
ensureUserPath(installDir, sharedOptions);
|
|
451
510
|
log(
|
|
452
511
|
`found vlink at ${vlinkPath}; installing vlink ${installModule.VERSION}`,
|
|
453
512
|
output
|
|
@@ -457,16 +516,39 @@ async function runInstaller(options = {}) {
|
|
|
457
516
|
}
|
|
458
517
|
|
|
459
518
|
const replacingExisting = Boolean(vlinkPath);
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
519
|
+
let installTarget = env.VLINK_INSTALL_DIR ? targetPath : (vlinkPath || targetPath);
|
|
520
|
+
// Avoid trying to replace a shared installation, even if its parent is writable.
|
|
521
|
+
if (vlinkPath && !env.VLINK_INSTALL_DIR && !samePath(vlinkPath, targetPath, platform)) {
|
|
522
|
+
verboseLog(`permission stage: checking ${vlinkPath} and its parent directory`, sharedOptions);
|
|
523
|
+
try {
|
|
524
|
+
fsImpl.accessSync(vlinkPath, fs.constants.W_OK);
|
|
525
|
+
fsImpl.accessSync(path.dirname(vlinkPath), fs.constants.W_OK);
|
|
526
|
+
verboseLog('permission stage: existing installation is replaceable', sharedOptions);
|
|
527
|
+
} catch (error) {
|
|
528
|
+
if (!PERMISSION_ERRORS.has(error.code)) throw error;
|
|
529
|
+
log(`cannot replace ${vlinkPath} (${error.code}); falling back to ${targetPath}`, output);
|
|
530
|
+
installTarget = targetPath;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
installTarget = path.resolve(installTarget);
|
|
534
|
+
log(`installation target: ${installTarget}`, output);
|
|
535
|
+
verboseLog('binary stage: resolving packaged or cached binary', sharedOptions);
|
|
536
|
+
const sourcePath = await ensureBinary({ verbose, output });
|
|
537
|
+
verboseLog(`binary stage: source=${sourcePath}`, sharedOptions);
|
|
538
|
+
try {
|
|
539
|
+
installBinary(sourcePath, installTarget, {
|
|
540
|
+
...sharedOptions,
|
|
541
|
+
stopDaemon: replacingExisting
|
|
542
|
+
});
|
|
543
|
+
} catch (error) {
|
|
544
|
+
// Permissions may change after the access check, or an ACL can deny rename.
|
|
545
|
+
if (env.VLINK_INSTALL_DIR || samePath(installTarget, targetPath, platform) ||
|
|
546
|
+
!PERMISSION_ERRORS.has(error.code)) throw error;
|
|
547
|
+
log(`cannot replace ${installTarget} (${error.code}); falling back to ${targetPath}`, output);
|
|
548
|
+
installTarget = targetPath;
|
|
549
|
+
installBinary(sourcePath, installTarget, sharedOptions);
|
|
550
|
+
}
|
|
551
|
+
if (samePath(path.dirname(installTarget), installDir, platform)) {
|
|
470
552
|
ensureUserPath(installDir, sharedOptions);
|
|
471
553
|
}
|
|
472
554
|
vlinkPath = installTarget;
|
|
@@ -476,30 +558,40 @@ async function runInstaller(options = {}) {
|
|
|
476
558
|
ensureBinary
|
|
477
559
|
});
|
|
478
560
|
const commandShimPath = installWindowsCommandShim(vlinkPath, sharedOptions);
|
|
561
|
+
verboseLog(
|
|
562
|
+
`shim stage: ${commandShimPath || 'not required on this platform'}`,
|
|
563
|
+
sharedOptions
|
|
564
|
+
);
|
|
479
565
|
installShellCompletion(vlinkPath, sharedOptions);
|
|
480
566
|
|
|
481
567
|
if (skipSetup) {
|
|
482
568
|
log('skipping TedBrain Agent setup', output);
|
|
483
569
|
} else {
|
|
484
570
|
log('starting TedBrain Agent setup', output);
|
|
571
|
+
verboseLog(`setup stage: executing ${vlinkPath} setup tedbrain`, sharedOptions);
|
|
485
572
|
const setup = spawnSyncImpl(vlinkPath, ['setup', 'tedbrain'], {
|
|
573
|
+
env,
|
|
486
574
|
stdio: 'inherit'
|
|
487
575
|
});
|
|
488
576
|
const failure = commandFailure(setup, 'vlink setup tedbrain');
|
|
489
577
|
if (failure) throw new VlinkSetupError(failure);
|
|
578
|
+
verboseLog('setup stage: completed successfully', sharedOptions);
|
|
490
579
|
}
|
|
491
580
|
|
|
581
|
+
log(`installation complete: ${vlinkPath}`, output);
|
|
582
|
+
log('open a new terminal to use the updated PATH', output);
|
|
492
583
|
return { installDir, vlinkPath, commandShimPath, setupSkipped: skipSetup };
|
|
493
584
|
}
|
|
494
585
|
|
|
495
586
|
function printInstallHelp(output = process.stdout) {
|
|
496
587
|
output.write(
|
|
497
588
|
[
|
|
498
|
-
'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]',
|
|
499
590
|
'',
|
|
500
591
|
'Installs or updates vlink and adds it to the user PATH.',
|
|
501
592
|
'',
|
|
502
|
-
' --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.'
|
|
503
595
|
].join('\n') + '\n'
|
|
504
596
|
);
|
|
505
597
|
}
|
|
@@ -511,18 +603,23 @@ async function runInstallerFromArgs(args, options = {}) {
|
|
|
511
603
|
}
|
|
512
604
|
|
|
513
605
|
let skipSetup = false;
|
|
606
|
+
let verbose = verboseEnabled(options);
|
|
514
607
|
for (const arg of args) {
|
|
515
608
|
if (arg === '--no-setup') {
|
|
516
609
|
skipSetup = true;
|
|
610
|
+
} else if (arg === '--verbose') {
|
|
611
|
+
verbose = true;
|
|
517
612
|
} else {
|
|
518
|
-
|
|
613
|
+
const displayArg = arg.startsWith('--') ? arg.split('=', 1)[0] : '<argument>';
|
|
614
|
+
throw new VlinkSetupError(`unknown install option: ${displayArg}`);
|
|
519
615
|
}
|
|
520
616
|
}
|
|
521
|
-
return runInstaller({ ...options, skipSetup });
|
|
617
|
+
return runInstaller({ ...options, skipSetup, verbose });
|
|
522
618
|
}
|
|
523
619
|
|
|
524
620
|
module.exports = {
|
|
525
621
|
VlinkSetupError,
|
|
622
|
+
verboseEnabled,
|
|
526
623
|
defaultInstallDir,
|
|
527
624
|
installPathFor,
|
|
528
625
|
isExecutableFile,
|
package/package.json
CHANGED