@automattic/vip 3.25.2 → 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.
@@ -30,9 +30,13 @@ const examples = [{
30
30
  usage: `${exampleUsage} --editor=phpstorm --slug=example-site`,
31
31
  description: 'Start a local environment and generate a Workspace file for developing in PhpStorm.'
32
32
  }];
33
- (0, _command.default)({
33
+ const cmd = (0, _command.default)({
34
34
  usage
35
- }).option('slug', 'A unique name for a local environment. Default is "vip-local".', undefined, _devEnvironmentCli.processSlug).option('skip-rebuild', 'Only start services that are not in a running state.').option(['w', 'skip-wp-versions-check'], 'Skip the prompt to update WordPress; occurs if the last major release version is not configured.').option('vscode', 'Generate a Visual Studio Code Workspace file (deprecated, use --editor=vscode instead).').option('editor', 'Generate a workspace file for the specified editor (supports: vscode, cursor, windsurf, phpstorm).').examples(examples).argv(process.argv, async (arg, opt) => {
35
+ }).option('slug', 'A unique name for a local environment. Default is "vip-local".', undefined, _devEnvironmentCli.processSlug).option('skip-rebuild', 'Only start services that are not in a running state.').option(['w', 'skip-wp-versions-check'], 'Skip the prompt to update WordPress; occurs if the last major release version is not configured.').option('vscode', 'Generate a Visual Studio Code Workspace file (deprecated, use --editor=vscode instead).').option('editor', 'Generate a workspace file for the specified editor (supports: vscode, cursor, windsurf, phpstorm).');
36
+ if (process.platform !== 'win32') {
37
+ cmd.option('autofix-domain-resolution', 'Automatically fix domain resolution issues by updating the hosts file [UNSAFE].');
38
+ }
39
+ cmd.examples(examples).argv(process.argv, async (arg, opt) => {
36
40
  const slug = await (0, _devEnvironmentCli.getEnvironmentName)(opt);
37
41
  const lando = await (0, _devEnvironmentLando.bootstrapLando)({
38
42
  logFile: (0, _devEnvironmentCli.getDevEnvLogFile)(slug)
@@ -49,7 +53,8 @@ const examples = [{
49
53
  debug('Args: ', arg, 'Options: ', opt);
50
54
  const options = {
51
55
  skipRebuild: Boolean(opt.skipRebuild),
52
- skipWpVersionsCheck: Boolean(opt.skipWpVersionsCheck)
56
+ skipWpVersionsCheck: Boolean(opt.skipWpVersionsCheck),
57
+ autofixDomains: Boolean(opt.autofixDomainResolution)
53
58
  };
54
59
  try {
55
60
  await (0, _devEnvironmentCore.startEnvironment)(lando, slug, options);
@@ -78,9 +78,9 @@ async function startEnvironment(lando, slug, options) {
78
78
  await (0, _devEnvironmentLando.removeProxyCache)(lando);
79
79
  }
80
80
  if (options.skipRebuild && !updated) {
81
- await (0, _devEnvironmentLando.landoStart)(lando, instancePath);
81
+ await (0, _devEnvironmentLando.landoStart)(lando, instancePath, options.autofixDomains);
82
82
  } else {
83
- await (0, _devEnvironmentLando.landoRebuild)(lando, instancePath);
83
+ await (0, _devEnvironmentLando.landoRebuild)(lando, instancePath, options.autofixDomains);
84
84
  }
85
85
  await printEnvironmentInfo(lando, slug, {
86
86
  extended: false
@@ -15,6 +15,7 @@ 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"));
@@ -35,6 +36,7 @@ var _devEnvironment = require("../constants/dev-environment");
35
36
  var _env = _interopRequireDefault(require("../env"));
36
37
  var _userError = _interopRequireDefault(require("../user-error"));
37
38
  var _xdgData = require("../xdg-data");
39
+ var _hostsUpdater = require("./hosts-updater");
38
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); }
39
41
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
40
42
  /**
@@ -323,11 +325,32 @@ async function bootstrapLando(options = {}) {
323
325
  debug('bootstrapLando() took %d ms', duration);
324
326
  }
325
327
  }
326
- async function landoStart(lando, instancePath) {
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) {
327
347
  const started = new Date();
328
348
  try {
329
349
  debug('Will start lando app on path:', instancePath);
330
350
  const app = await getLandoApplication(lando, instancePath);
351
+ if (autoFixDomainResolution) {
352
+ installDnsAutofixer(app);
353
+ }
331
354
  await app.start();
332
355
  } finally {
333
356
  const duration = new Date().getTime() - started.getTime();
@@ -351,11 +374,14 @@ async function landoLogs(lando, instancePath, options) {
351
374
  debug('landoLogs() took %d ms', duration);
352
375
  }
353
376
  }
354
- async function landoRebuild(lando, instancePath) {
377
+ async function landoRebuild(lando, instancePath, autoFixDomainResolution = false) {
355
378
  const started = new Date();
356
379
  try {
357
380
  debug('Will rebuild lando app on path:', instancePath);
358
381
  const app = await getLandoApplication(lando, instancePath);
382
+ if (autoFixDomainResolution) {
383
+ installDnsAutofixer(app);
384
+ }
359
385
  await ensureNoOrphantProxyContainer(lando);
360
386
  await app.rebuild();
361
387
  } finally {
@@ -529,7 +555,7 @@ async function getExtraServicesConnections(lando, app) {
529
555
  }
530
556
  return extraServices;
531
557
  }
532
- async function tryResolveDomains(urls) {
558
+ async function tryResolveDomains(urls, autofix) {
533
559
  const domains = [...new Set(urls.filter(url => url.toLowerCase().startsWith('http')).map(url => {
534
560
  try {
535
561
  return new URL(url).hostname;
@@ -538,6 +564,7 @@ async function tryResolveDomains(urls) {
538
564
  }
539
565
  }).filter(domain => domain !== undefined))];
540
566
  const domainsToFix = [];
567
+ const pendingWarnings = [];
541
568
  for (const domain of domains) {
542
569
  try {
543
570
  // eslint-disable-next-line no-await-in-loop
@@ -545,20 +572,72 @@ async function tryResolveDomains(urls) {
545
572
  debug('%s resolves to %s', domain, address.address);
546
573
  if (address.address !== '127.0.0.1') {
547
574
  domainsToFix.push(domain);
548
- console.warn(_chalk.default.yellow.bold('WARNING:'), `${domain} resolves to ${address.address} instead of 127.0.0.1. Things may not work as expected.`);
575
+ pendingWarnings.push(`${domain} resolves to ${address.address} instead of 127.0.0.1. Things may not work as expected.`);
549
576
  }
550
577
  } catch (err) {
551
578
  const msg = err instanceof Error ? err.message : 'Unknown error';
552
579
  domainsToFix.push(domain);
553
- console.warn(_chalk.default.yellow.bold('WARNING:'), `Failed to resolve ${domain}: ${msg}`);
580
+ pendingWarnings.push(`Failed to resolve ${domain}: ${msg}`);
554
581
  }
555
582
  }
556
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
+ }
557
602
  console.warn(_chalk.default.yellow('Please add the following lines to the hosts file on your system:\n'));
558
603
  console.warn(domainsToFix.map(domain => `127.0.0.1 ${domain}`).join('\n'));
559
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'));
560
605
  }
561
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
+ }
562
641
  async function getRunningServicesForProject(docker, project) {
563
642
  const containers = await docker.listContainers({
564
643
  filters: {
@@ -579,7 +658,7 @@ async function checkEnvHealth(lando, app) {
579
658
  });
580
659
  });
581
660
  const urlsToScan = Object.keys(urls).filter(url => !url.includes('*'));
582
- await tryResolveDomains(urlsToScan);
661
+ await tryResolveDomains(urlsToScan, false);
583
662
  app.urls.forEach(entry => {
584
663
  // We use different status codes to see if the service is up.
585
664
  // We may consider the service is up when Lando considers it is down.
@@ -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
+ }
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@automattic/vip",
3
- "version": "3.25.2",
3
+ "version": "3.25.3-dev.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@automattic/vip",
9
- "version": "3.25.2",
9
+ "version": "3.25.3-dev.0",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT",
12
12
  "dependencies": {
13
- "@apollo/client": "4.1.8",
13
+ "@apollo/client": "4.1.9",
14
14
  "@automattic/vip-search-replace": "^2.0.0",
15
15
  "@json2csv/plainjs": "^7.0.3",
16
16
  "@wwa/single-line-log": "^1.1.4",
@@ -143,15 +143,10 @@
143
143
  }
144
144
  },
145
145
  "node_modules/@apollo/client": {
146
- "version": "4.1.8",
147
- "resolved": "https://registry.npmjs.org/@apollo/client/-/client-4.1.8.tgz",
148
- "integrity": "sha512-0joBqkgwxDTzAni0azryXQjbSIKBFqfzEa1eh8KX6aWl4RHWAlfNwZAaWlIpOYWTeRnSZu2glnyGiHGPMAAerA==",
146
+ "version": "4.1.9",
147
+ "resolved": "https://registry.npmjs.org/@apollo/client/-/client-4.1.9.tgz",
148
+ "integrity": "sha512-qfpkQD51tdU/7iAR6aLb4w9o/L7I475DluWHRb61U/3Q0AH29nNOxOBHjBbWDdf16ncPOoQuxne1sEs2NjqBFw==",
149
149
  "license": "MIT",
150
- "workspaces": [
151
- "dist",
152
- "codegen",
153
- "scripts/codemods/ac3-to-ac4"
154
- ],
155
150
  "dependencies": {
156
151
  "@graphql-typed-document-node/core": "^3.1.1",
157
152
  "@wry/caches": "^1.0.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/vip",
3
- "version": "3.25.2",
3
+ "version": "3.25.3-dev.0",
4
4
  "description": "The VIP Javascript library & CLI",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -138,7 +138,7 @@
138
138
  "typescript": "^6.0.2"
139
139
  },
140
140
  "dependencies": {
141
- "@apollo/client": "4.1.8",
141
+ "@apollo/client": "4.1.9",
142
142
  "@automattic/vip-search-replace": "^2.0.0",
143
143
  "@json2csv/plainjs": "^7.0.3",
144
144
  "@wwa/single-line-log": "^1.1.4",