@automattic/vip 3.25.2-dev.0 → 3.25.3-dev.0
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/AGENTS.md +0 -6
- package/CLAUDE.md +0 -1
- package/dist/bin/vip-db-phpmyadmin.js +11 -4
- package/dist/bin/vip-dev-env-exec.js +1 -18
- package/dist/bin/vip-dev-env-start.js +8 -3
- package/dist/bin/vip.js +69 -111
- package/dist/commands/phpmyadmin.js +64 -16
- package/dist/lib/cli/command.js +53 -224
- package/dist/lib/cli/config.js +5 -13
- package/dist/lib/cli/exit.js +1 -2
- package/dist/lib/dev-environment/dev-environment-cli.js +2 -9
- package/dist/lib/dev-environment/dev-environment-core.js +6 -64
- package/dist/lib/dev-environment/dev-environment-lando.js +95 -53
- package/dist/lib/dev-environment/hosts-updater.js +184 -0
- package/npm-shrinkwrap.json +669 -933
- package/package.json +11 -14
- package/tsconfig.json +1 -7
- package/dist/bin/vip-sea.js +0 -19
- package/dist/lib/cli/internal-bin-loader.js +0 -81
- package/dist/lib/cli/runtime-mode.js +0 -21
- package/dist/lib/cli/sea-dispatch.js +0 -88
- package/dist/lib/cli/sea-runtime.js +0 -75
- package/dist/lib/dev-environment/lando-loader.js +0 -48
- package/docs/COMMANDER-MIGRATION.md +0 -55
- package/docs/SEA-BUILD-SIGNING.md +0 -171
- package/helpers/build-sea.js +0 -167
|
@@ -15,9 +15,14 @@ exports.landoShell = landoShell;
|
|
|
15
15
|
exports.landoStart = landoStart;
|
|
16
16
|
exports.landoStop = landoStop;
|
|
17
17
|
exports.removeProxyCache = removeProxyCache;
|
|
18
|
+
exports.tryResolveDomains = tryResolveDomains;
|
|
18
19
|
exports.validateDockerInstalled = validateDockerInstalled;
|
|
19
20
|
var _chalk = _interopRequireDefault(require("chalk"));
|
|
20
21
|
var _debug = _interopRequireDefault(require("debug"));
|
|
22
|
+
var _bootstrap = require("lando/lib/bootstrap");
|
|
23
|
+
var _lando = _interopRequireDefault(require("lando/lib/lando"));
|
|
24
|
+
var _utils = _interopRequireDefault(require("lando/plugins/lando-core/lib/utils"));
|
|
25
|
+
var _build = _interopRequireDefault(require("lando/plugins/lando-tooling/lib/build"));
|
|
21
26
|
var _nodeChild_process = require("node:child_process");
|
|
22
27
|
var _promises = require("node:dns/promises");
|
|
23
28
|
var _promises2 = require("node:fs/promises");
|
|
@@ -27,12 +32,11 @@ var _nodeUtil = require("node:util");
|
|
|
27
32
|
var _semver = require("semver");
|
|
28
33
|
var _devEnvironmentCore = require("./dev-environment-core");
|
|
29
34
|
var _dockerUtils = require("./docker-utils");
|
|
30
|
-
var _landoLoader = require("./lando-loader");
|
|
31
|
-
var _runtimeMode = require("../cli/runtime-mode");
|
|
32
35
|
var _devEnvironment = require("../constants/dev-environment");
|
|
33
36
|
var _env = _interopRequireDefault(require("../env"));
|
|
34
37
|
var _userError = _interopRequireDefault(require("../user-error"));
|
|
35
38
|
var _xdgData = require("../xdg-data");
|
|
39
|
+
var _hostsUpdater = require("./hosts-updater");
|
|
36
40
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
37
41
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
38
42
|
/**
|
|
@@ -41,44 +45,6 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
|
|
|
41
45
|
const DEBUG_KEY = '@automattic/vip:bin:dev-environment';
|
|
42
46
|
const debug = (0, _debug.default)(DEBUG_KEY);
|
|
43
47
|
const execFileAsync = (0, _nodeUtil.promisify)(_nodeChild_process.execFile);
|
|
44
|
-
const unwrapLandoModuleDefault = loaded => {
|
|
45
|
-
if (loaded && typeof loaded === 'object' && 'default' in loaded) {
|
|
46
|
-
return loaded.default;
|
|
47
|
-
}
|
|
48
|
-
return loaded;
|
|
49
|
-
};
|
|
50
|
-
let landoConstructor = null;
|
|
51
|
-
let landoBuildTaskFn = null;
|
|
52
|
-
let landoUtilsModule = null;
|
|
53
|
-
let buildConfigFn = null;
|
|
54
|
-
const getLandoConstructor = () => {
|
|
55
|
-
if (!landoConstructor) {
|
|
56
|
-
landoConstructor = unwrapLandoModuleDefault((0, _landoLoader.loadLandoModule)('lando/lib/lando'));
|
|
57
|
-
}
|
|
58
|
-
return landoConstructor;
|
|
59
|
-
};
|
|
60
|
-
const getLandoBuildTask = () => {
|
|
61
|
-
if (!landoBuildTaskFn) {
|
|
62
|
-
landoBuildTaskFn = unwrapLandoModuleDefault((0, _landoLoader.loadLandoModule)('lando/plugins/lando-tooling/lib/build'));
|
|
63
|
-
}
|
|
64
|
-
return landoBuildTaskFn;
|
|
65
|
-
};
|
|
66
|
-
const getLandoUtils = () => {
|
|
67
|
-
if (!landoUtilsModule) {
|
|
68
|
-
landoUtilsModule = unwrapLandoModuleDefault((0, _landoLoader.loadLandoModule)('lando/plugins/lando-core/lib/utils'));
|
|
69
|
-
}
|
|
70
|
-
return landoUtilsModule;
|
|
71
|
-
};
|
|
72
|
-
const getLandoBuildConfig = () => {
|
|
73
|
-
if (!buildConfigFn) {
|
|
74
|
-
const loaded = (0, _landoLoader.loadLandoModule)('lando/lib/bootstrap');
|
|
75
|
-
buildConfigFn = loaded.buildConfig ?? loaded.default?.buildConfig ?? null;
|
|
76
|
-
if (!buildConfigFn) {
|
|
77
|
-
throw new Error('Unable to load Lando bootstrap buildConfig.');
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return buildConfigFn;
|
|
81
|
-
};
|
|
82
48
|
const bannerLabelWidth = 18;
|
|
83
49
|
let logPathRegistered = false;
|
|
84
50
|
let resolvedLogPath = null;
|
|
@@ -182,7 +148,7 @@ const writeLogBanner = async config => {
|
|
|
182
148
|
});
|
|
183
149
|
const dockerVersions = await getDockerVersions(config);
|
|
184
150
|
const command = process.argv.slice(1).join(' ');
|
|
185
|
-
const bannerLines = ['=== VIP Dev Env Log ===', formatBannerLine('COMMAND', command), formatBannerLine('OS', `${_env.default.os.name} ${_env.default.os.version} ${_env.default.os.arch}`), formatBannerLine('NODE', _env.default.node.version), formatBannerLine('VIP-CLI', _env.default.app.version), formatBannerLine('
|
|
151
|
+
const bannerLines = ['=== VIP Dev Env Log ===', formatBannerLine('COMMAND', command), formatBannerLine('OS', `${_env.default.os.name} ${_env.default.os.version} ${_env.default.os.arch}`), formatBannerLine('NODE', _env.default.node.version), formatBannerLine('VIP-CLI', _env.default.app.version), formatBannerLine('DOCKER ENGINE', dockerVersions.engine), formatBannerLine('DOCKER COMPOSE', dockerVersions.compose), formatBannerLine('COMPOSE PLUGIN', dockerVersions.composePlugin), formatBannerLine('DOCKER BIN', config.dockerBin ?? 'unknown'), formatBannerLine('COMPOSE BIN', config.composeBin ?? 'unknown'), formatBannerLine('RAM', formatBytes((0, _nodeOs.totalmem)())), formatBannerLine('CPU', String((0, _nodeOs.cpus)().length)), '===', '', ''];
|
|
186
152
|
await (0, _promises2.writeFile)(logFilePath, bannerLines.join('\n'), {
|
|
187
153
|
flag: 'a'
|
|
188
154
|
});
|
|
@@ -193,7 +159,7 @@ const writeLogBanner = async config => {
|
|
|
193
159
|
*/
|
|
194
160
|
async function getLandoConfig(options = {}) {
|
|
195
161
|
// The path will be smth like `yarn/global/node_modules/lando/lib/lando.js`; we need the path up to `lando` (inclusive)
|
|
196
|
-
const landoPath = (0, _nodePath.dirname)((0, _nodePath.dirname)(
|
|
162
|
+
const landoPath = (0, _nodePath.dirname)((0, _nodePath.dirname)(require.resolve('lando')));
|
|
197
163
|
debug(`Getting Lando config, using paths '${landoPath}' for plugins`);
|
|
198
164
|
const isLandoDebugSelected = _debug.default.enabled(DEBUG_KEY);
|
|
199
165
|
const isAllDebugSelected = _debug.default.enabled('"*"');
|
|
@@ -237,7 +203,7 @@ async function getLandoConfig(options = {}) {
|
|
|
237
203
|
LANDO_HOST_GROUP_ID: process.platform === 'win32' ? '1000' : `${(0, _nodeOs.userInfo)().gid}`
|
|
238
204
|
}
|
|
239
205
|
};
|
|
240
|
-
return
|
|
206
|
+
return (0, _bootstrap.buildConfig)(config);
|
|
241
207
|
}
|
|
242
208
|
const appMap = new Map();
|
|
243
209
|
async function initLandoApplication(lando, instancePath) {
|
|
@@ -326,8 +292,7 @@ async function bootstrapLando(options = {}) {
|
|
|
326
292
|
}
|
|
327
293
|
registerLogPathOutput(config, Boolean(options.quiet));
|
|
328
294
|
await writeLogBanner(config);
|
|
329
|
-
const
|
|
330
|
-
const lando = new LandoClass(config);
|
|
295
|
+
const lando = new _lando.default(config);
|
|
331
296
|
_debug.default.log = (message, ...args) => {
|
|
332
297
|
lando.log.debug(message, ...args);
|
|
333
298
|
};
|
|
@@ -360,11 +325,32 @@ async function bootstrapLando(options = {}) {
|
|
|
360
325
|
debug('bootstrapLando() took %d ms', duration);
|
|
361
326
|
}
|
|
362
327
|
}
|
|
363
|
-
|
|
328
|
+
const autofixedApps = new WeakSet();
|
|
329
|
+
function installDnsAutofixer(app) {
|
|
330
|
+
if (autofixedApps.has(app)) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
autofixedApps.add(app);
|
|
334
|
+
app.events.on('post-start', 9, async () => {
|
|
335
|
+
const urlsToScan = [];
|
|
336
|
+
app.info.filter(service => service.urls.length).forEach(service => {
|
|
337
|
+
service.urls.forEach(url => {
|
|
338
|
+
if (!/^https?:\/\/(localhost|127\.0\.0\.1):/.exec(url) && !url.includes('*')) {
|
|
339
|
+
urlsToScan.push(url);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
await tryResolveDomains(urlsToScan, true);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
async function landoStart(lando, instancePath, autoFixDomainResolution = false) {
|
|
364
347
|
const started = new Date();
|
|
365
348
|
try {
|
|
366
349
|
debug('Will start lando app on path:', instancePath);
|
|
367
350
|
const app = await getLandoApplication(lando, instancePath);
|
|
351
|
+
if (autoFixDomainResolution) {
|
|
352
|
+
installDnsAutofixer(app);
|
|
353
|
+
}
|
|
368
354
|
await app.start();
|
|
369
355
|
} finally {
|
|
370
356
|
const duration = new Date().getTime() - started.getTime();
|
|
@@ -388,11 +374,14 @@ async function landoLogs(lando, instancePath, options) {
|
|
|
388
374
|
debug('landoLogs() took %d ms', duration);
|
|
389
375
|
}
|
|
390
376
|
}
|
|
391
|
-
async function landoRebuild(lando, instancePath) {
|
|
377
|
+
async function landoRebuild(lando, instancePath, autoFixDomainResolution = false) {
|
|
392
378
|
const started = new Date();
|
|
393
379
|
try {
|
|
394
380
|
debug('Will rebuild lando app on path:', instancePath);
|
|
395
381
|
const app = await getLandoApplication(lando, instancePath);
|
|
382
|
+
if (autoFixDomainResolution) {
|
|
383
|
+
installDnsAutofixer(app);
|
|
384
|
+
}
|
|
396
385
|
await ensureNoOrphantProxyContainer(lando);
|
|
397
386
|
await app.rebuild();
|
|
398
387
|
} finally {
|
|
@@ -462,7 +451,7 @@ async function landoInfo(lando, instancePath, options = {}) {
|
|
|
462
451
|
const started = new Date();
|
|
463
452
|
try {
|
|
464
453
|
const app = await getLandoApplication(lando, instancePath);
|
|
465
|
-
const info =
|
|
454
|
+
const info = _utils.default.startTable(app);
|
|
466
455
|
const reachableServices = app.info.filter(service => service.urls.length);
|
|
467
456
|
reachableServices.forEach(service => info[`${service.service} urls`] = service.urls);
|
|
468
457
|
const health = await checkEnvHealth(lando, app);
|
|
@@ -566,7 +555,7 @@ async function getExtraServicesConnections(lando, app) {
|
|
|
566
555
|
}
|
|
567
556
|
return extraServices;
|
|
568
557
|
}
|
|
569
|
-
async function tryResolveDomains(urls) {
|
|
558
|
+
async function tryResolveDomains(urls, autofix) {
|
|
570
559
|
const domains = [...new Set(urls.filter(url => url.toLowerCase().startsWith('http')).map(url => {
|
|
571
560
|
try {
|
|
572
561
|
return new URL(url).hostname;
|
|
@@ -575,6 +564,7 @@ async function tryResolveDomains(urls) {
|
|
|
575
564
|
}
|
|
576
565
|
}).filter(domain => domain !== undefined))];
|
|
577
566
|
const domainsToFix = [];
|
|
567
|
+
const pendingWarnings = [];
|
|
578
568
|
for (const domain of domains) {
|
|
579
569
|
try {
|
|
580
570
|
// eslint-disable-next-line no-await-in-loop
|
|
@@ -582,20 +572,72 @@ async function tryResolveDomains(urls) {
|
|
|
582
572
|
debug('%s resolves to %s', domain, address.address);
|
|
583
573
|
if (address.address !== '127.0.0.1') {
|
|
584
574
|
domainsToFix.push(domain);
|
|
585
|
-
|
|
575
|
+
pendingWarnings.push(`${domain} resolves to ${address.address} instead of 127.0.0.1. Things may not work as expected.`);
|
|
586
576
|
}
|
|
587
577
|
} catch (err) {
|
|
588
578
|
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
589
579
|
domainsToFix.push(domain);
|
|
590
|
-
|
|
580
|
+
pendingWarnings.push(`Failed to resolve ${domain}: ${msg}`);
|
|
591
581
|
}
|
|
592
582
|
}
|
|
593
583
|
if (domainsToFix.length) {
|
|
584
|
+
if (autofix) {
|
|
585
|
+
console.log(_chalk.default.green('Attempting to fix domain resolution issues...'));
|
|
586
|
+
const result = await autofixDomains(domainsToFix);
|
|
587
|
+
if (result instanceof Error) {
|
|
588
|
+
// Autofix failed — surface the original DNS warnings so the user knows what went wrong
|
|
589
|
+
pendingWarnings.forEach(msg => console.warn(_chalk.default.yellow.bold('WARNING:'), msg));
|
|
590
|
+
console.error(_chalk.default.red(result.message));
|
|
591
|
+
if (result.cause) {
|
|
592
|
+
console.error(_chalk.default.red('Cause: '), result.cause.message);
|
|
593
|
+
}
|
|
594
|
+
} else {
|
|
595
|
+
// Autofix succeeded — suppress the warnings so a clean start looks clean
|
|
596
|
+
console.log(_chalk.default.green('Domain resolution issues fixed successfully.'));
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
} else {
|
|
600
|
+
pendingWarnings.forEach(msg => console.warn(_chalk.default.yellow.bold('WARNING:'), msg));
|
|
601
|
+
}
|
|
594
602
|
console.warn(_chalk.default.yellow('Please add the following lines to the hosts file on your system:\n'));
|
|
595
603
|
console.warn(domainsToFix.map(domain => `127.0.0.1 ${domain}`).join('\n'));
|
|
596
604
|
console.warn(_chalk.default.yellow('\nLearn more: https://docs.wpvip.com/vip-local-development-environment/troubleshooting-dev-env/#h-resolve-networking-configuration-issues\n'));
|
|
597
605
|
}
|
|
598
606
|
}
|
|
607
|
+
function ensureError(error) {
|
|
608
|
+
if (error instanceof Error) {
|
|
609
|
+
return error;
|
|
610
|
+
}
|
|
611
|
+
return new Error(String(error));
|
|
612
|
+
}
|
|
613
|
+
const AUTOFIX_DOWNLOAD_TIMEOUT_MS = 30_000;
|
|
614
|
+
async function autofixDomains(domains) {
|
|
615
|
+
const dir = await (0, _hostsUpdater.getInstallDir)();
|
|
616
|
+
const filename = (0, _hostsUpdater.getExeName)();
|
|
617
|
+
let binary = _nodePath.default.join(dir, filename);
|
|
618
|
+
try {
|
|
619
|
+
await (0, _promises2.access)(binary, _promises2.constants.X_OK);
|
|
620
|
+
} catch {
|
|
621
|
+
try {
|
|
622
|
+
binary = await (0, _hostsUpdater.installBinary)('latest', dir, AUTOFIX_DOWNLOAD_TIMEOUT_MS);
|
|
623
|
+
} catch (err) {
|
|
624
|
+
return new Error('Failed to install hosts updater binary, cannot autofix domain resolution issues.', {
|
|
625
|
+
cause: ensureError(err)
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
const fixableDomains = domains.filter(domain => !domain.includes('*'));
|
|
630
|
+
if (fixableDomains.length) {
|
|
631
|
+
try {
|
|
632
|
+
await (0, _hostsUpdater.updateDomains)(binary, fixableDomains);
|
|
633
|
+
} catch (err) {
|
|
634
|
+
return new Error('Failed to update hosts file, cannot autofix domain resolution issues.', {
|
|
635
|
+
cause: ensureError(err)
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
599
641
|
async function getRunningServicesForProject(docker, project) {
|
|
600
642
|
const containers = await docker.listContainers({
|
|
601
643
|
filters: {
|
|
@@ -616,7 +658,7 @@ async function checkEnvHealth(lando, app) {
|
|
|
616
658
|
});
|
|
617
659
|
});
|
|
618
660
|
const urlsToScan = Object.keys(urls).filter(url => !url.includes('*'));
|
|
619
|
-
await tryResolveDomains(urlsToScan);
|
|
661
|
+
await tryResolveDomains(urlsToScan, false);
|
|
620
662
|
app.urls.forEach(entry => {
|
|
621
663
|
// We use different status codes to see if the service is up.
|
|
622
664
|
// We may consider the service is up when Lando considers it is down.
|
|
@@ -675,7 +717,7 @@ async function landoExec(lando, instancePath, toolName, args, options) {
|
|
|
675
717
|
if (options.stdio) {
|
|
676
718
|
tool.stdio = options.stdio;
|
|
677
719
|
}
|
|
678
|
-
const task =
|
|
720
|
+
const task = (0, _build.default)(tool, lando);
|
|
679
721
|
const argv = {
|
|
680
722
|
// eslint-disable-next-line id-length
|
|
681
723
|
_: args
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.InvalidChecksumError = exports.DownloadError = void 0;
|
|
5
|
+
exports.download = download;
|
|
6
|
+
exports.getExeName = getExeName;
|
|
7
|
+
exports.getInstallDir = getInstallDir;
|
|
8
|
+
exports.getReleaseUrl = getReleaseUrl;
|
|
9
|
+
exports.installBinary = installBinary;
|
|
10
|
+
exports.updateDomains = updateDomains;
|
|
11
|
+
var _nodeFetch = _interopRequireDefault(require("node-fetch"));
|
|
12
|
+
var _nodeChild_process = require("node:child_process");
|
|
13
|
+
var _nodeCrypto = require("node:crypto");
|
|
14
|
+
var _nodeFs = require("node:fs");
|
|
15
|
+
var _promises = require("node:fs/promises");
|
|
16
|
+
var _nodeOs = require("node:os");
|
|
17
|
+
var _nodePath = require("node:path");
|
|
18
|
+
var _nodeStream = require("node:stream");
|
|
19
|
+
var _promises2 = require("node:stream/promises");
|
|
20
|
+
var _nodeUtil = require("node:util");
|
|
21
|
+
var _nodeZlib = require("node:zlib");
|
|
22
|
+
var _proxyAgent = require("../http/proxy-agent");
|
|
23
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
24
|
+
class DownloadError extends Error {
|
|
25
|
+
constructor(url, code, options) {
|
|
26
|
+
super((0, _nodeUtil.format)('Failed to download file: %s (status code: %d)', url, code), options);
|
|
27
|
+
this.name = 'DownloadError';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.DownloadError = DownloadError;
|
|
31
|
+
class InvalidChecksumError extends Error {
|
|
32
|
+
constructor(message, options) {
|
|
33
|
+
super(message, options);
|
|
34
|
+
this.name = 'InvalidChecksumError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.InvalidChecksumError = InvalidChecksumError;
|
|
38
|
+
const archMap = {
|
|
39
|
+
ia32: '386',
|
|
40
|
+
x64: 'amd64',
|
|
41
|
+
arm64: 'arm64'
|
|
42
|
+
};
|
|
43
|
+
const platformMap = {
|
|
44
|
+
win32: 'windows',
|
|
45
|
+
darwin: 'darwin',
|
|
46
|
+
linux: 'linux'
|
|
47
|
+
};
|
|
48
|
+
function getReleaseUrl(version = 'latest', arch = process.arch, platform = process.platform) {
|
|
49
|
+
const resolvedArch = archMap[arch];
|
|
50
|
+
const resolvedPlatform = platformMap[platform];
|
|
51
|
+
if (!resolvedArch || !resolvedPlatform) {
|
|
52
|
+
throw new Error('Unsupported platform or architecture');
|
|
53
|
+
}
|
|
54
|
+
const suffix = 'windows' === resolvedPlatform ? '.exe' : '';
|
|
55
|
+
if (version !== 'latest') {
|
|
56
|
+
const binary = `https://github.com/Automattic/dev-env-update-hosts/releases/download/${version}/dev-env-update-hosts-${resolvedPlatform}-${resolvedArch}${suffix}.gz`;
|
|
57
|
+
const checksum = `${binary}.sum`;
|
|
58
|
+
return [binary, checksum];
|
|
59
|
+
}
|
|
60
|
+
const binary = `https://github.com/Automattic/dev-env-update-hosts/releases/latest/download/dev-env-update-hosts-${resolvedPlatform}-${resolvedArch}${suffix}.gz`;
|
|
61
|
+
const checksum = `${binary}.sum`;
|
|
62
|
+
return [binary, checksum];
|
|
63
|
+
}
|
|
64
|
+
async function download(url, asText, timeout = 0) {
|
|
65
|
+
const controller = new AbortController();
|
|
66
|
+
const timeoutId = timeout > 0 ? setTimeout(() => controller.abort(), timeout) : null;
|
|
67
|
+
const clearTimer = () => {
|
|
68
|
+
if (timeoutId) {
|
|
69
|
+
clearTimeout(timeoutId);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
const proxyAgent = (0, _proxyAgent.createProxyAgent)(url.toString());
|
|
73
|
+
let response;
|
|
74
|
+
try {
|
|
75
|
+
response = await (0, _nodeFetch.default)(url, {
|
|
76
|
+
signal: controller.signal,
|
|
77
|
+
redirect: 'follow',
|
|
78
|
+
agent: proxyAgent ?? undefined
|
|
79
|
+
});
|
|
80
|
+
} catch (err) {
|
|
81
|
+
clearTimer();
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
84
|
+
if (!response.ok) {
|
|
85
|
+
clearTimer();
|
|
86
|
+
throw new DownloadError(url, response.status);
|
|
87
|
+
}
|
|
88
|
+
if (asText) {
|
|
89
|
+
try {
|
|
90
|
+
return await response.text();
|
|
91
|
+
} finally {
|
|
92
|
+
clearTimer();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// For streams: unref the timer so it will not prevent process exit once the
|
|
97
|
+
// body has been fully consumed, but it will still abort a stalled read while
|
|
98
|
+
// the event loop is kept alive by the active stream pipeline.
|
|
99
|
+
timeoutId?.unref();
|
|
100
|
+
return response.body;
|
|
101
|
+
}
|
|
102
|
+
function getExeName(platform = process.platform, arch = process.arch) {
|
|
103
|
+
const exeSuffix = platform === 'win32' ? '.exe' : '';
|
|
104
|
+
return `dev-env-update-host-${platform}-${arch}${exeSuffix}`;
|
|
105
|
+
}
|
|
106
|
+
async function installBinary(version, dest, timeout = 0, arch = process.arch, platform = process.platform) {
|
|
107
|
+
const [binaryUrl, checksumUrl] = getReleaseUrl(version, arch, platform);
|
|
108
|
+
const checksum = (await download(new URL(checksumUrl), true, timeout)).trim();
|
|
109
|
+
const compressedStream = await download(new URL(binaryUrl), false, timeout);
|
|
110
|
+
if (!compressedStream) {
|
|
111
|
+
throw new Error('Failed to download binary');
|
|
112
|
+
}
|
|
113
|
+
const hash = (0, _nodeCrypto.createHash)('sha256');
|
|
114
|
+
const hashTap = new _nodeStream.Transform({
|
|
115
|
+
transform(chunk, _encoding, callback) {
|
|
116
|
+
hash.update(chunk);
|
|
117
|
+
callback(null, chunk);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
const destFilename = (0, _nodePath.join)(dest, getExeName(platform, arch));
|
|
121
|
+
// Use a unique temp name to avoid collisions when multiple processes install concurrently.
|
|
122
|
+
const tempFilename = `${destFilename}.${(0, _nodeCrypto.randomBytes)(8).toString('hex')}.tmp`;
|
|
123
|
+
const outStream = (0, _nodeFs.createWriteStream)(tempFilename, {
|
|
124
|
+
mode: 0o755
|
|
125
|
+
});
|
|
126
|
+
let removeTmp = true;
|
|
127
|
+
try {
|
|
128
|
+
await (0, _promises2.pipeline)(compressedStream, hashTap, (0, _nodeZlib.createGunzip)(), outStream);
|
|
129
|
+
const calculatedChecksum = hash.digest('hex');
|
|
130
|
+
if (!(0, _nodeCrypto.timingSafeEqual)(Buffer.from(calculatedChecksum, 'hex'), Buffer.from(checksum, 'hex'))) {
|
|
131
|
+
throw new InvalidChecksumError((0, _nodeUtil.format)('Downloaded file checksum does not match expected value (expected: %s, got: %s)', checksum, calculatedChecksum));
|
|
132
|
+
}
|
|
133
|
+
await (0, _promises.rename)(tempFilename, destFilename);
|
|
134
|
+
removeTmp = false;
|
|
135
|
+
} finally {
|
|
136
|
+
if (removeTmp) {
|
|
137
|
+
await (0, _promises.rm)(tempFilename, {
|
|
138
|
+
force: true
|
|
139
|
+
}).catch(err => {
|
|
140
|
+
console.warn('Error removing temporary file %s: %s', tempFilename, err);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return destFilename;
|
|
145
|
+
}
|
|
146
|
+
async function getInstallDir() {
|
|
147
|
+
const binDir = (0, _nodePath.join)((0, _nodePath.dirname)(__dirname), 'bin');
|
|
148
|
+
try {
|
|
149
|
+
await (0, _promises.mkdir)(binDir, {
|
|
150
|
+
recursive: true
|
|
151
|
+
});
|
|
152
|
+
await (0, _promises.access)(binDir, _promises.constants.W_OK);
|
|
153
|
+
return binDir;
|
|
154
|
+
} catch {
|
|
155
|
+
// Swallow errors and fall back to a temporary directory
|
|
156
|
+
}
|
|
157
|
+
const tmpDir = await (0, _promises.mkdtemp)((0, _nodePath.join)((0, _nodeOs.tmpdir)(), 'dev-env-update-hosts-'));
|
|
158
|
+
process.once('exit', () => {
|
|
159
|
+
try {
|
|
160
|
+
(0, _nodeFs.rmSync)(tmpDir, {
|
|
161
|
+
recursive: true,
|
|
162
|
+
force: true
|
|
163
|
+
});
|
|
164
|
+
} catch (err) {
|
|
165
|
+
console.warn('Error removing temporary dir: %s', err);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
return tmpDir;
|
|
169
|
+
}
|
|
170
|
+
function updateDomains(binary, domains) {
|
|
171
|
+
return new Promise((resolve, reject) => {
|
|
172
|
+
const child = (0, _nodeChild_process.spawn)(binary, domains, {
|
|
173
|
+
stdio: 'inherit'
|
|
174
|
+
});
|
|
175
|
+
child.on('error', err => reject(err));
|
|
176
|
+
child.on('exit', code => {
|
|
177
|
+
if (code === 0) {
|
|
178
|
+
resolve();
|
|
179
|
+
} else {
|
|
180
|
+
reject(new Error(`Binary exited with code ${code}`));
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
}
|