@automattic/vip 3.25.3-dev.0 → 4.0.1
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 +6 -0
- package/CLAUDE.md +1 -0
- package/dist/bin/vip-dev-env-exec.js +18 -1
- package/dist/bin/vip-dev-env-start.js +3 -8
- package/dist/bin/vip-sea.js +19 -0
- package/dist/bin/vip.js +111 -69
- package/dist/lib/cli/command.js +249 -53
- package/dist/lib/cli/config.js +13 -5
- package/dist/lib/cli/exit.js +2 -1
- package/dist/lib/cli/internal-bin-loader.js +81 -0
- package/dist/lib/cli/runtime-mode.js +21 -0
- package/dist/lib/cli/sea-dispatch.js +88 -0
- package/dist/lib/cli/sea-runtime.js +75 -0
- package/dist/lib/dev-environment/dev-environment-cli.js +9 -2
- package/dist/lib/dev-environment/dev-environment-core.js +69 -10
- package/dist/lib/dev-environment/dev-environment-lando.js +54 -95
- package/dist/lib/dev-environment/lando-loader.js +48 -0
- package/docs/COMMANDER-MIGRATION.md +55 -0
- package/docs/SEA-BUILD-SIGNING.md +171 -0
- package/helpers/build-sea.js +167 -0
- package/npm-shrinkwrap.json +505 -127
- package/package.json +6 -3
- package/dist/lib/dev-environment/hosts-updater.js +0 -184
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.prepareSeaRuntimeFilesystem = prepareSeaRuntimeFilesystem;
|
|
5
|
+
var _nodeFs = require("node:fs");
|
|
6
|
+
var _promises = require("node:fs/promises");
|
|
7
|
+
var _nodePath = _interopRequireDefault(require("node:path"));
|
|
8
|
+
var _package = _interopRequireDefault(require("../../../package.json"));
|
|
9
|
+
var _xdgData = require("../xdg-data");
|
|
10
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
|
+
const RUNTIME_ARCHIVE_KEY = 'sea.node_modules.tgz';
|
|
12
|
+
const RUNTIME_DIR_NAME = 'sea-runtime';
|
|
13
|
+
const READY_FILE_NAME = '.ready';
|
|
14
|
+
const ARCHIVE_FILE_NAME = 'node_modules.tgz';
|
|
15
|
+
async function getSeaModule() {
|
|
16
|
+
try {
|
|
17
|
+
return await import('node:sea');
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function getRuntimeRootPath() {
|
|
23
|
+
return _nodePath.default.join((0, _xdgData.xdgData)(), 'vip', RUNTIME_DIR_NAME, _package.default.version);
|
|
24
|
+
}
|
|
25
|
+
function getRuntimeNodeModulesPath(runtimeRootPath) {
|
|
26
|
+
return _nodePath.default.join(runtimeRootPath, 'node_modules');
|
|
27
|
+
}
|
|
28
|
+
function getRuntimeReadyPath(runtimeRootPath) {
|
|
29
|
+
return _nodePath.default.join(runtimeRootPath, READY_FILE_NAME);
|
|
30
|
+
}
|
|
31
|
+
function appendNodePath(nodeModulesPath) {
|
|
32
|
+
const existing = process.env.NODE_PATH ? process.env.NODE_PATH.split(_nodePath.default.delimiter) : [];
|
|
33
|
+
if (!existing.includes(nodeModulesPath)) {
|
|
34
|
+
process.env.NODE_PATH = [nodeModulesPath, ...existing].join(_nodePath.default.delimiter);
|
|
35
|
+
}
|
|
36
|
+
const Module = require('node:module');
|
|
37
|
+
Module.Module._initPaths();
|
|
38
|
+
const runtimeEntryPath = _nodePath.default.join(nodeModulesPath, '..', '__sea-entry__.js');
|
|
39
|
+
const runtimeRequire = Module.createRequire(runtimeEntryPath);
|
|
40
|
+
module.filename = runtimeEntryPath;
|
|
41
|
+
module.paths = Module._nodeModulePaths(_nodePath.default.dirname(runtimeEntryPath));
|
|
42
|
+
module.require = runtimeRequire;
|
|
43
|
+
}
|
|
44
|
+
async function extractRuntimeDependencies(runtimeRootPath, archiveBuffer) {
|
|
45
|
+
await (0, _promises.rm)(runtimeRootPath, {
|
|
46
|
+
recursive: true,
|
|
47
|
+
force: true
|
|
48
|
+
});
|
|
49
|
+
await (0, _promises.mkdir)(runtimeRootPath, {
|
|
50
|
+
recursive: true
|
|
51
|
+
});
|
|
52
|
+
const archivePath = _nodePath.default.join(runtimeRootPath, ARCHIVE_FILE_NAME);
|
|
53
|
+
await (0, _promises.writeFile)(archivePath, archiveBuffer);
|
|
54
|
+
const tar = require('tar');
|
|
55
|
+
await tar.x({
|
|
56
|
+
file: archivePath,
|
|
57
|
+
cwd: runtimeRootPath
|
|
58
|
+
});
|
|
59
|
+
await (0, _promises.writeFile)(getRuntimeReadyPath(runtimeRootPath), _package.default.version, 'utf8');
|
|
60
|
+
}
|
|
61
|
+
async function prepareSeaRuntimeFilesystem() {
|
|
62
|
+
const sea = await getSeaModule();
|
|
63
|
+
if (!sea?.isSea?.() || !sea.getAsset) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const runtimeRootPath = getRuntimeRootPath();
|
|
67
|
+
const runtimeNodeModulesPath = getRuntimeNodeModulesPath(runtimeRootPath);
|
|
68
|
+
const runtimeReadyPath = getRuntimeReadyPath(runtimeRootPath);
|
|
69
|
+
if (!(0, _nodeFs.existsSync)(runtimeReadyPath) || !(0, _nodeFs.existsSync)(runtimeNodeModulesPath)) {
|
|
70
|
+
const archiveAsset = sea.getAsset(RUNTIME_ARCHIVE_KEY);
|
|
71
|
+
const archiveBuffer = Buffer.isBuffer(archiveAsset) ? archiveAsset : Buffer.from(archiveAsset);
|
|
72
|
+
await extractRuntimeDependencies(runtimeRootPath, archiveBuffer);
|
|
73
|
+
}
|
|
74
|
+
appendNodePath(runtimeNodeModulesPath);
|
|
75
|
+
}
|
|
@@ -38,7 +38,6 @@ var _chalk = _interopRequireDefault(require("chalk"));
|
|
|
38
38
|
var _child_process = require("child_process");
|
|
39
39
|
var _debug = _interopRequireDefault(require("debug"));
|
|
40
40
|
var _enquirer = require("enquirer");
|
|
41
|
-
var _formatters = _interopRequireDefault(require("lando/lib/formatters"));
|
|
42
41
|
var _nodeFs = require("node:fs");
|
|
43
42
|
var _nodeOs = require("node:os");
|
|
44
43
|
var _path = _interopRequireDefault(require("path"));
|
|
@@ -46,6 +45,7 @@ var _shelljs = require("shelljs");
|
|
|
46
45
|
var _devEnvironmentConfigurationFile = require("./dev-environment-configuration-file");
|
|
47
46
|
var _devEnvironmentCore = require("./dev-environment-core");
|
|
48
47
|
var _devEnvironmentLando = require("./dev-environment-lando");
|
|
48
|
+
var _landoLoader = require("./lando-loader");
|
|
49
49
|
var _user = require("../api/user");
|
|
50
50
|
var _devEnvironment = require("../constants/dev-environment");
|
|
51
51
|
var _tracker = require("../tracker");
|
|
@@ -55,6 +55,13 @@ const debug = (0, _debug.default)('@automattic/vip:bin:dev-environment');
|
|
|
55
55
|
const DEFAULT_SLUG = exports.DEFAULT_SLUG = 'vip-local';
|
|
56
56
|
const CONFIGURATION_FOLDER = exports.CONFIGURATION_FOLDER = '.wpvip';
|
|
57
57
|
let isStdinTTY = Boolean(process.stdin.isTTY);
|
|
58
|
+
let landoFormatters = null;
|
|
59
|
+
const getLandoFormatters = () => {
|
|
60
|
+
if (!landoFormatters) {
|
|
61
|
+
landoFormatters = (0, _landoLoader.loadLandoModule)('lando/lib/formatters');
|
|
62
|
+
}
|
|
63
|
+
return landoFormatters;
|
|
64
|
+
};
|
|
58
65
|
|
|
59
66
|
/**
|
|
60
67
|
* Used internally for tests
|
|
@@ -150,7 +157,7 @@ function getEnvironmentStartCommand(slug, configurationFileOptions) {
|
|
|
150
157
|
return `${_devEnvironment.DEV_ENVIRONMENT_FULL_COMMAND} start --slug ${slug}`;
|
|
151
158
|
}
|
|
152
159
|
function printTable(data) {
|
|
153
|
-
const formattedData =
|
|
160
|
+
const formattedData = getLandoFormatters().formatData(data, {
|
|
154
161
|
format: 'table'
|
|
155
162
|
}, {
|
|
156
163
|
border: false
|
|
@@ -28,7 +28,6 @@ var _debug = _interopRequireDefault(require("debug"));
|
|
|
28
28
|
var _ejs = _interopRequireDefault(require("ejs"));
|
|
29
29
|
var _enquirer = require("enquirer");
|
|
30
30
|
var _graphql = require("graphql");
|
|
31
|
-
var _utils = require("lando/lib/utils");
|
|
32
31
|
var _nodeFetch = _interopRequireDefault(require("node-fetch"));
|
|
33
32
|
var _nodeCrypto = require("node:crypto");
|
|
34
33
|
var _nodeFs = _interopRequireDefault(require("node:fs"));
|
|
@@ -37,6 +36,7 @@ var _nodePath = _interopRequireDefault(require("node:path"));
|
|
|
37
36
|
var _semver = _interopRequireDefault(require("semver"));
|
|
38
37
|
var _devEnvironmentCli = require("./dev-environment-cli");
|
|
39
38
|
var _devEnvironmentLando = require("./dev-environment-lando");
|
|
39
|
+
var _landoLoader = require("./lando-loader");
|
|
40
40
|
var _app = _interopRequireDefault(require("../api/app"));
|
|
41
41
|
var _software = require("../config/software");
|
|
42
42
|
var _devEnvironment = require("../constants/dev-environment");
|
|
@@ -47,7 +47,9 @@ var _xdgData = require("../xdg-data");
|
|
|
47
47
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
48
48
|
const debug = (0, _debug.default)('@automattic/vip:bin:dev-environment');
|
|
49
49
|
const landoFileTemplatePath = _nodePath.default.join(__dirname, '..', '..', '..', 'assets', 'dev-env.lando.template.yml.ejs');
|
|
50
|
+
const landoTemplateAssetKey = 'dev-env.lando.template.yml.ejs';
|
|
50
51
|
const nginxFileTemplatePath = _nodePath.default.join(__dirname, '..', '..', '..', 'assets', 'dev-env.nginx.template.conf.ejs');
|
|
52
|
+
const nginxTemplateAssetKey = 'dev-env.nginx.template.conf.ejs';
|
|
51
53
|
const landoFileName = '.lando.yml';
|
|
52
54
|
const landoOverridesFileName = '.lando.local.yml';
|
|
53
55
|
const landoBackupFileName = '.lando.backup.yml';
|
|
@@ -58,6 +60,53 @@ const integrationsConfigBackupFileName = 'integrations.json.bak';
|
|
|
58
60
|
const uploadPathString = 'uploads';
|
|
59
61
|
const nginxPathString = 'nginx';
|
|
60
62
|
const integrationsConfigPathString = 'integrations-config';
|
|
63
|
+
const STARTUP_READY_ATTEMPTS = 6;
|
|
64
|
+
const STARTUP_READY_DELAY_MS = 2000;
|
|
65
|
+
let dockerComposifyFromLando = null;
|
|
66
|
+
const dockerComposify = value => {
|
|
67
|
+
if (!dockerComposifyFromLando) {
|
|
68
|
+
const landoUtils = (0, _landoLoader.loadLandoModule)('lando/lib/utils');
|
|
69
|
+
dockerComposifyFromLando = landoUtils.dockerComposify;
|
|
70
|
+
}
|
|
71
|
+
return dockerComposifyFromLando(value);
|
|
72
|
+
};
|
|
73
|
+
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
74
|
+
let seaModulePromise = null;
|
|
75
|
+
const getSeaModule = async () => {
|
|
76
|
+
if (!seaModulePromise) {
|
|
77
|
+
seaModulePromise = (async () => {
|
|
78
|
+
try {
|
|
79
|
+
return await import('node:sea');
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
})();
|
|
84
|
+
}
|
|
85
|
+
return seaModulePromise;
|
|
86
|
+
};
|
|
87
|
+
const renderTemplateFile = async (filePath, assetKey, templateData) => {
|
|
88
|
+
const sea = await getSeaModule();
|
|
89
|
+
if (sea?.isSea?.() && sea.getAsset) {
|
|
90
|
+
const template = sea.getAsset(assetKey, 'utf8');
|
|
91
|
+
if (typeof template === 'string') {
|
|
92
|
+
return _ejs.default.render(template, templateData);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return _ejs.default.renderFile(filePath, templateData);
|
|
96
|
+
};
|
|
97
|
+
async function waitForEnvironmentToBeUp(lando, instancePath) {
|
|
98
|
+
return pollEnvironmentUpStatus(lando, instancePath, 1);
|
|
99
|
+
}
|
|
100
|
+
async function pollEnvironmentUpStatus(lando, instancePath, attempt) {
|
|
101
|
+
if (await (0, _devEnvironmentLando.isEnvUp)(lando, instancePath)) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
if (attempt >= STARTUP_READY_ATTEMPTS) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
await sleep(STARTUP_READY_DELAY_MS);
|
|
108
|
+
return pollEnvironmentUpStatus(lando, instancePath, attempt + 1);
|
|
109
|
+
}
|
|
61
110
|
async function startEnvironment(lando, slug, options) {
|
|
62
111
|
debug('Will start an environment', slug);
|
|
63
112
|
const instancePath = getEnvironmentPath(slug);
|
|
@@ -78,9 +127,18 @@ async function startEnvironment(lando, slug, options) {
|
|
|
78
127
|
await (0, _devEnvironmentLando.removeProxyCache)(lando);
|
|
79
128
|
}
|
|
80
129
|
if (options.skipRebuild && !updated) {
|
|
81
|
-
await (0, _devEnvironmentLando.landoStart)(lando, instancePath
|
|
130
|
+
await (0, _devEnvironmentLando.landoStart)(lando, instancePath);
|
|
82
131
|
} else {
|
|
83
|
-
await (0, _devEnvironmentLando.landoRebuild)(lando, instancePath
|
|
132
|
+
await (0, _devEnvironmentLando.landoRebuild)(lando, instancePath);
|
|
133
|
+
}
|
|
134
|
+
let isEnvironmentUp = await waitForEnvironmentToBeUp(lando, instancePath);
|
|
135
|
+
if (!isEnvironmentUp) {
|
|
136
|
+
// A second startup pass helps recover after Docker network auto-cleanup edge cases.
|
|
137
|
+
await (0, _devEnvironmentLando.landoStart)(lando, instancePath);
|
|
138
|
+
isEnvironmentUp = await waitForEnvironmentToBeUp(lando, instancePath);
|
|
139
|
+
}
|
|
140
|
+
if (!isEnvironmentUp) {
|
|
141
|
+
throw new _userError.default(`Environment "${slug}" did not reach a running state. Please try "${_chalk.default.bold(`vip dev-env start --slug ${slug}`)}" again.`);
|
|
84
142
|
}
|
|
85
143
|
await printEnvironmentInfo(lando, slug, {
|
|
86
144
|
extended: false
|
|
@@ -166,7 +224,7 @@ async function destroyEnvironment(lando, slug, removeFiles) {
|
|
|
166
224
|
} else {
|
|
167
225
|
debug("Lando file doesn't exist, skipping lando destroy.");
|
|
168
226
|
}
|
|
169
|
-
await _nodeFs.default.promises.rm(_nodePath.default.join((0, _xdgData.xdgData)(), 'vip', 'lando', 'compose',
|
|
227
|
+
await _nodeFs.default.promises.rm(_nodePath.default.join((0, _xdgData.xdgData)(), 'vip', 'lando', 'compose', dockerComposify(slug)), {
|
|
170
228
|
force: true,
|
|
171
229
|
recursive: true
|
|
172
230
|
});
|
|
@@ -209,11 +267,12 @@ function parseComponentForInfo(component) {
|
|
|
209
267
|
async function showLogs(lando, slug, options = {}) {
|
|
210
268
|
debug('Will display logs command on env', slug, 'with options', options);
|
|
211
269
|
const instancePath = getEnvironmentPath(slug);
|
|
212
|
-
debug('Instance path for', slug,
|
|
270
|
+
debug('Instance path for %s is %s', slug, instancePath);
|
|
213
271
|
if (options.service) {
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
272
|
+
const application = await (0, _devEnvironmentLando.getLandoApplication)(lando, instancePath);
|
|
273
|
+
const services = application.info.map(service => service.service);
|
|
274
|
+
if (!services.includes(options.service)) {
|
|
275
|
+
throw new _userError.default(`Service '${options.service}' not found. Please choose from: ${services.join(', ')}`);
|
|
217
276
|
}
|
|
218
277
|
}
|
|
219
278
|
return (0, _devEnvironmentLando.landoLogs)(lando, instancePath, options);
|
|
@@ -349,8 +408,8 @@ async function prepareLandoEnv(lando, instanceData, instancePath, integrationsCo
|
|
|
349
408
|
...instanceData,
|
|
350
409
|
domain: lando.config.domain
|
|
351
410
|
};
|
|
352
|
-
const landoFile = await
|
|
353
|
-
const nginxFile = await
|
|
411
|
+
const landoFile = await renderTemplateFile(landoFileTemplatePath, landoTemplateAssetKey, templateData);
|
|
412
|
+
const nginxFile = await renderTemplateFile(nginxFileTemplatePath, nginxTemplateAssetKey, templateData);
|
|
354
413
|
const instanceDataFile = JSON.stringify(instanceData);
|
|
355
414
|
const landoFileTargetPath = _nodePath.default.join(instancePath, landoFileName);
|
|
356
415
|
const landoOverridesFileTargetPath = _nodePath.default.join(instancePath, landoOverridesFileName);
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
exports.__esModule = true;
|
|
4
4
|
exports.bootstrapLando = bootstrapLando;
|
|
5
5
|
exports.checkEnvHealth = checkEnvHealth;
|
|
6
|
+
exports.getLandoApplication = getLandoApplication;
|
|
6
7
|
exports.getProxyContainer = getProxyContainer;
|
|
7
8
|
exports.isContainerRunning = isContainerRunning;
|
|
8
9
|
exports.isEnvUp = isEnvUp;
|
|
@@ -15,14 +16,9 @@ exports.landoShell = landoShell;
|
|
|
15
16
|
exports.landoStart = landoStart;
|
|
16
17
|
exports.landoStop = landoStop;
|
|
17
18
|
exports.removeProxyCache = removeProxyCache;
|
|
18
|
-
exports.tryResolveDomains = tryResolveDomains;
|
|
19
19
|
exports.validateDockerInstalled = validateDockerInstalled;
|
|
20
20
|
var _chalk = _interopRequireDefault(require("chalk"));
|
|
21
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"));
|
|
26
22
|
var _nodeChild_process = require("node:child_process");
|
|
27
23
|
var _promises = require("node:dns/promises");
|
|
28
24
|
var _promises2 = require("node:fs/promises");
|
|
@@ -32,11 +28,12 @@ var _nodeUtil = require("node:util");
|
|
|
32
28
|
var _semver = require("semver");
|
|
33
29
|
var _devEnvironmentCore = require("./dev-environment-core");
|
|
34
30
|
var _dockerUtils = require("./docker-utils");
|
|
31
|
+
var _landoLoader = require("./lando-loader");
|
|
32
|
+
var _runtimeMode = require("../cli/runtime-mode");
|
|
35
33
|
var _devEnvironment = require("../constants/dev-environment");
|
|
36
34
|
var _env = _interopRequireDefault(require("../env"));
|
|
37
35
|
var _userError = _interopRequireDefault(require("../user-error"));
|
|
38
36
|
var _xdgData = require("../xdg-data");
|
|
39
|
-
var _hostsUpdater = require("./hosts-updater");
|
|
40
37
|
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); }
|
|
41
38
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
42
39
|
/**
|
|
@@ -45,6 +42,44 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
|
|
|
45
42
|
const DEBUG_KEY = '@automattic/vip:bin:dev-environment';
|
|
46
43
|
const debug = (0, _debug.default)(DEBUG_KEY);
|
|
47
44
|
const execFileAsync = (0, _nodeUtil.promisify)(_nodeChild_process.execFile);
|
|
45
|
+
const unwrapLandoModuleDefault = loaded => {
|
|
46
|
+
if (loaded && typeof loaded === 'object' && 'default' in loaded) {
|
|
47
|
+
return loaded.default;
|
|
48
|
+
}
|
|
49
|
+
return loaded;
|
|
50
|
+
};
|
|
51
|
+
let landoConstructor = null;
|
|
52
|
+
let landoBuildTaskFn = null;
|
|
53
|
+
let landoUtilsModule = null;
|
|
54
|
+
let buildConfigFn = null;
|
|
55
|
+
const getLandoConstructor = () => {
|
|
56
|
+
if (!landoConstructor) {
|
|
57
|
+
landoConstructor = unwrapLandoModuleDefault((0, _landoLoader.loadLandoModule)('lando/lib/lando'));
|
|
58
|
+
}
|
|
59
|
+
return landoConstructor;
|
|
60
|
+
};
|
|
61
|
+
const getLandoBuildTask = () => {
|
|
62
|
+
if (!landoBuildTaskFn) {
|
|
63
|
+
landoBuildTaskFn = unwrapLandoModuleDefault((0, _landoLoader.loadLandoModule)('lando/plugins/lando-tooling/lib/build'));
|
|
64
|
+
}
|
|
65
|
+
return landoBuildTaskFn;
|
|
66
|
+
};
|
|
67
|
+
const getLandoUtils = () => {
|
|
68
|
+
if (!landoUtilsModule) {
|
|
69
|
+
landoUtilsModule = unwrapLandoModuleDefault((0, _landoLoader.loadLandoModule)('lando/plugins/lando-core/lib/utils'));
|
|
70
|
+
}
|
|
71
|
+
return landoUtilsModule;
|
|
72
|
+
};
|
|
73
|
+
const getLandoBuildConfig = () => {
|
|
74
|
+
if (!buildConfigFn) {
|
|
75
|
+
const loaded = (0, _landoLoader.loadLandoModule)('lando/lib/bootstrap');
|
|
76
|
+
buildConfigFn = loaded.buildConfig ?? loaded.default?.buildConfig ?? null;
|
|
77
|
+
if (!buildConfigFn) {
|
|
78
|
+
throw new Error('Unable to load Lando bootstrap buildConfig.');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return buildConfigFn;
|
|
82
|
+
};
|
|
48
83
|
const bannerLabelWidth = 18;
|
|
49
84
|
let logPathRegistered = false;
|
|
50
85
|
let resolvedLogPath = null;
|
|
@@ -148,7 +183,7 @@ const writeLogBanner = async config => {
|
|
|
148
183
|
});
|
|
149
184
|
const dockerVersions = await getDockerVersions(config);
|
|
150
185
|
const command = process.argv.slice(1).join(' ');
|
|
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
|
+
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('RUNTIME', (0, _runtimeMode.getRuntimeModeLabel)()), 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)), '===', '', ''];
|
|
152
187
|
await (0, _promises2.writeFile)(logFilePath, bannerLines.join('\n'), {
|
|
153
188
|
flag: 'a'
|
|
154
189
|
});
|
|
@@ -159,7 +194,7 @@ const writeLogBanner = async config => {
|
|
|
159
194
|
*/
|
|
160
195
|
async function getLandoConfig(options = {}) {
|
|
161
196
|
// The path will be smth like `yarn/global/node_modules/lando/lib/lando.js`; we need the path up to `lando` (inclusive)
|
|
162
|
-
const landoPath = (0, _nodePath.dirname)((0, _nodePath.dirname)(
|
|
197
|
+
const landoPath = (0, _nodePath.dirname)((0, _nodePath.dirname)((0, _landoLoader.resolveLandoModule)('lando')));
|
|
163
198
|
debug(`Getting Lando config, using paths '${landoPath}' for plugins`);
|
|
164
199
|
const isLandoDebugSelected = _debug.default.enabled(DEBUG_KEY);
|
|
165
200
|
const isAllDebugSelected = _debug.default.enabled('"*"');
|
|
@@ -203,7 +238,7 @@ async function getLandoConfig(options = {}) {
|
|
|
203
238
|
LANDO_HOST_GROUP_ID: process.platform === 'win32' ? '1000' : `${(0, _nodeOs.userInfo)().gid}`
|
|
204
239
|
}
|
|
205
240
|
};
|
|
206
|
-
return (
|
|
241
|
+
return getLandoBuildConfig()(config);
|
|
207
242
|
}
|
|
208
243
|
const appMap = new Map();
|
|
209
244
|
async function initLandoApplication(lando, instancePath) {
|
|
@@ -292,7 +327,8 @@ async function bootstrapLando(options = {}) {
|
|
|
292
327
|
}
|
|
293
328
|
registerLogPathOutput(config, Boolean(options.quiet));
|
|
294
329
|
await writeLogBanner(config);
|
|
295
|
-
const
|
|
330
|
+
const LandoClass = getLandoConstructor();
|
|
331
|
+
const lando = new LandoClass(config);
|
|
296
332
|
_debug.default.log = (message, ...args) => {
|
|
297
333
|
lando.log.debug(message, ...args);
|
|
298
334
|
};
|
|
@@ -325,32 +361,11 @@ async function bootstrapLando(options = {}) {
|
|
|
325
361
|
debug('bootstrapLando() took %d ms', duration);
|
|
326
362
|
}
|
|
327
363
|
}
|
|
328
|
-
|
|
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
|
+
async function landoStart(lando, instancePath) {
|
|
347
365
|
const started = new Date();
|
|
348
366
|
try {
|
|
349
367
|
debug('Will start lando app on path:', instancePath);
|
|
350
368
|
const app = await getLandoApplication(lando, instancePath);
|
|
351
|
-
if (autoFixDomainResolution) {
|
|
352
|
-
installDnsAutofixer(app);
|
|
353
|
-
}
|
|
354
369
|
await app.start();
|
|
355
370
|
} finally {
|
|
356
371
|
const duration = new Date().getTime() - started.getTime();
|
|
@@ -374,14 +389,11 @@ async function landoLogs(lando, instancePath, options) {
|
|
|
374
389
|
debug('landoLogs() took %d ms', duration);
|
|
375
390
|
}
|
|
376
391
|
}
|
|
377
|
-
async function landoRebuild(lando, instancePath
|
|
392
|
+
async function landoRebuild(lando, instancePath) {
|
|
378
393
|
const started = new Date();
|
|
379
394
|
try {
|
|
380
395
|
debug('Will rebuild lando app on path:', instancePath);
|
|
381
396
|
const app = await getLandoApplication(lando, instancePath);
|
|
382
|
-
if (autoFixDomainResolution) {
|
|
383
|
-
installDnsAutofixer(app);
|
|
384
|
-
}
|
|
385
397
|
await ensureNoOrphantProxyContainer(lando);
|
|
386
398
|
await app.rebuild();
|
|
387
399
|
} finally {
|
|
@@ -451,7 +463,7 @@ async function landoInfo(lando, instancePath, options = {}) {
|
|
|
451
463
|
const started = new Date();
|
|
452
464
|
try {
|
|
453
465
|
const app = await getLandoApplication(lando, instancePath);
|
|
454
|
-
const info =
|
|
466
|
+
const info = getLandoUtils().startTable(app);
|
|
455
467
|
const reachableServices = app.info.filter(service => service.urls.length);
|
|
456
468
|
reachableServices.forEach(service => info[`${service.service} urls`] = service.urls);
|
|
457
469
|
const health = await checkEnvHealth(lando, app);
|
|
@@ -555,7 +567,7 @@ async function getExtraServicesConnections(lando, app) {
|
|
|
555
567
|
}
|
|
556
568
|
return extraServices;
|
|
557
569
|
}
|
|
558
|
-
async function tryResolveDomains(urls
|
|
570
|
+
async function tryResolveDomains(urls) {
|
|
559
571
|
const domains = [...new Set(urls.filter(url => url.toLowerCase().startsWith('http')).map(url => {
|
|
560
572
|
try {
|
|
561
573
|
return new URL(url).hostname;
|
|
@@ -564,7 +576,6 @@ async function tryResolveDomains(urls, autofix) {
|
|
|
564
576
|
}
|
|
565
577
|
}).filter(domain => domain !== undefined))];
|
|
566
578
|
const domainsToFix = [];
|
|
567
|
-
const pendingWarnings = [];
|
|
568
579
|
for (const domain of domains) {
|
|
569
580
|
try {
|
|
570
581
|
// eslint-disable-next-line no-await-in-loop
|
|
@@ -572,72 +583,20 @@ async function tryResolveDomains(urls, autofix) {
|
|
|
572
583
|
debug('%s resolves to %s', domain, address.address);
|
|
573
584
|
if (address.address !== '127.0.0.1') {
|
|
574
585
|
domainsToFix.push(domain);
|
|
575
|
-
|
|
586
|
+
console.warn(_chalk.default.yellow.bold('WARNING:'), `${domain} resolves to ${address.address} instead of 127.0.0.1. Things may not work as expected.`);
|
|
576
587
|
}
|
|
577
588
|
} catch (err) {
|
|
578
589
|
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
579
590
|
domainsToFix.push(domain);
|
|
580
|
-
|
|
591
|
+
console.warn(_chalk.default.yellow.bold('WARNING:'), `Failed to resolve ${domain}: ${msg}`);
|
|
581
592
|
}
|
|
582
593
|
}
|
|
583
594
|
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
|
-
}
|
|
602
595
|
console.warn(_chalk.default.yellow('Please add the following lines to the hosts file on your system:\n'));
|
|
603
596
|
console.warn(domainsToFix.map(domain => `127.0.0.1 ${domain}`).join('\n'));
|
|
604
597
|
console.warn(_chalk.default.yellow('\nLearn more: https://docs.wpvip.com/vip-local-development-environment/troubleshooting-dev-env/#h-resolve-networking-configuration-issues\n'));
|
|
605
598
|
}
|
|
606
599
|
}
|
|
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
|
-
}
|
|
641
600
|
async function getRunningServicesForProject(docker, project) {
|
|
642
601
|
const containers = await docker.listContainers({
|
|
643
602
|
filters: {
|
|
@@ -658,7 +617,7 @@ async function checkEnvHealth(lando, app) {
|
|
|
658
617
|
});
|
|
659
618
|
});
|
|
660
619
|
const urlsToScan = Object.keys(urls).filter(url => !url.includes('*'));
|
|
661
|
-
await tryResolveDomains(urlsToScan
|
|
620
|
+
await tryResolveDomains(urlsToScan);
|
|
662
621
|
app.urls.forEach(entry => {
|
|
663
622
|
// We use different status codes to see if the service is up.
|
|
664
623
|
// We may consider the service is up when Lando considers it is down.
|
|
@@ -717,7 +676,7 @@ async function landoExec(lando, instancePath, toolName, args, options) {
|
|
|
717
676
|
if (options.stdio) {
|
|
718
677
|
tool.stdio = options.stdio;
|
|
719
678
|
}
|
|
720
|
-
const task = (
|
|
679
|
+
const task = getLandoBuildTask()(tool, lando);
|
|
721
680
|
const argv = {
|
|
722
681
|
// eslint-disable-next-line id-length
|
|
723
682
|
_: args
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.loadLandoModule = loadLandoModule;
|
|
5
|
+
exports.resolveLandoModule = resolveLandoModule;
|
|
6
|
+
var _nodeFs = require("node:fs");
|
|
7
|
+
var _nodeModule = require("node:module");
|
|
8
|
+
var _nodePath = _interopRequireDefault(require("node:path"));
|
|
9
|
+
var _package = _interopRequireDefault(require("../../../package.json"));
|
|
10
|
+
var _xdgData = require("../xdg-data");
|
|
11
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
|
+
const SEA_RUNTIME_DIR_NAME = 'sea-runtime';
|
|
13
|
+
let cachedRequire = null;
|
|
14
|
+
let didResolveRequire = false;
|
|
15
|
+
const baseRequire = (0, _nodeModule.createRequire)(__filename);
|
|
16
|
+
function isSeaRuntime() {
|
|
17
|
+
try {
|
|
18
|
+
const sea = baseRequire('node:sea');
|
|
19
|
+
return Boolean(sea?.isSea?.());
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function getSeaRuntimeNodeModulesPath() {
|
|
25
|
+
return _nodePath.default.join((0, _xdgData.xdgData)(), 'vip', SEA_RUNTIME_DIR_NAME, _package.default.version, 'node_modules');
|
|
26
|
+
}
|
|
27
|
+
function getRuntimeRequire() {
|
|
28
|
+
if (didResolveRequire && cachedRequire) {
|
|
29
|
+
return cachedRequire;
|
|
30
|
+
}
|
|
31
|
+
didResolveRequire = true;
|
|
32
|
+
if (isSeaRuntime()) {
|
|
33
|
+
const runtimeNodeModulesPath = getSeaRuntimeNodeModulesPath();
|
|
34
|
+
if ((0, _nodeFs.existsSync)(runtimeNodeModulesPath)) {
|
|
35
|
+
const runtimeEntryPath = _nodePath.default.join(runtimeNodeModulesPath, '..', '__sea-entry__.js');
|
|
36
|
+
cachedRequire = (0, _nodeModule.createRequire)(runtimeEntryPath);
|
|
37
|
+
return cachedRequire;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
cachedRequire = baseRequire;
|
|
41
|
+
return cachedRequire;
|
|
42
|
+
}
|
|
43
|
+
function loadLandoModule(request) {
|
|
44
|
+
return getRuntimeRequire()(request);
|
|
45
|
+
}
|
|
46
|
+
function resolveLandoModule(request) {
|
|
47
|
+
return getRuntimeRequire().resolve(request);
|
|
48
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Commander Migration Status
|
|
2
|
+
|
|
3
|
+
Goal: remove the abandoned `args` package, keep CLI behavior stable, and support packaging to a single bundled executable (Node SEA or similar). See `AGENTS.md` for broader architecture traps.
|
|
4
|
+
|
|
5
|
+
## Migration Outcome
|
|
6
|
+
|
|
7
|
+
- `src/lib/cli/command.js` is the active Commander-backed compatibility wrapper for all bins that call `command()`.
|
|
8
|
+
- `args` has been removed from `package.json` and `npm-shrinkwrap.json`.
|
|
9
|
+
- Root command flow (`src/bin/vip.js`) now dispatches via the shared Commander wrapper again, preserving login gating and subcommand chaining.
|
|
10
|
+
- Temporary side-path wrapper work has been removed (`src/lib/cli/command-commander.ts` deleted).
|
|
11
|
+
|
|
12
|
+
## Compatibility Behaviors Preserved
|
|
13
|
+
|
|
14
|
+
- Legacy command contract stays the same: `command(opts).option(...).argv(process.argv, handler)`.
|
|
15
|
+
- Alias behavior remains pre-parse: `@app` and `@app.env` are stripped before `--`; alias + `--app/--env` still errors.
|
|
16
|
+
- `_opts` controls are still honored: app/env context fetch, confirmation gating, output formatting, wildcard command handling, required positional args.
|
|
17
|
+
- Shared formatting/output and telemetry hooks are still in the wrapper path.
|
|
18
|
+
- Local nested subcommand dispatch still works via sibling executable resolution.
|
|
19
|
+
|
|
20
|
+
## Post-Migration Hardening
|
|
21
|
+
|
|
22
|
+
- Short-flag compatibility was restored for long options without explicit aliases:
|
|
23
|
+
- Example: `--elasticsearch` accepts `-e`, `--phpmyadmin` accepts `-p`, etc.
|
|
24
|
+
- Auto-short generation skips reserved global flags (`-h`, `-v`, `-d`) and avoids duplicate collisions.
|
|
25
|
+
- `vip dev-env exec` now performs bounded readiness checks before deciding an environment is not running.
|
|
26
|
+
- `startEnvironment()` now includes bounded post-start readiness checks and one recovery `landoStart` retry if the environment stays `DOWN` after rebuild/start.
|
|
27
|
+
|
|
28
|
+
## Remaining Technical Debt
|
|
29
|
+
|
|
30
|
+
- `_opts` is still module-level state in `src/lib/cli/command.js` and can leak between command instances in one process.
|
|
31
|
+
- Help text parity against historical `args` output is close but still a verification target for high-traffic commands.
|
|
32
|
+
- Full dev-env E2E can still show Docker/Lando infrastructure flakiness (network cleanup and startup latency), which is environment-level, not parser-level.
|
|
33
|
+
|
|
34
|
+
## Verification Commands
|
|
35
|
+
|
|
36
|
+
- `npm run build`
|
|
37
|
+
- `npm run check-types`
|
|
38
|
+
- `npm run test:e2e:dev-env -- --runInBand __tests__/devenv-e2e/001-create.spec.js __tests__/devenv-e2e/005-update.spec.js`
|
|
39
|
+
- `npm run test:e2e:dev-env -- --runInBand __tests__/devenv-e2e/008-exec.spec.js __tests__/devenv-e2e/010-import-sql.spec.js`
|
|
40
|
+
|
|
41
|
+
## Single-Bundle Direction
|
|
42
|
+
|
|
43
|
+
- Preferred: `esbuild` bundle rooted at `src/bin/vip.js`.
|
|
44
|
+
- Keep native deps external (`@postman/node-keytar`) for SEA/packaging workflows.
|
|
45
|
+
- Candidate build target:
|
|
46
|
+
- `dist/vip.bundle.cjs` for SEA ingestion or launcher wrapping.
|
|
47
|
+
- Example command:
|
|
48
|
+
- `esbuild src/bin/vip.js --bundle --platform=node --target=node20 --format=cjs --outfile=dist/vip.bundle.cjs --banner:js="#!/usr/bin/env node" --external:@postman/node-keytar`
|
|
49
|
+
|
|
50
|
+
## Next Refactor Steps
|
|
51
|
+
|
|
52
|
+
1. Remove global `_opts` state from `src/lib/cli/command.js` by moving to per-instance configuration.
|
|
53
|
+
2. Add parser contract tests for aliasing, wildcard behavior, and short/long boolean coercion.
|
|
54
|
+
3. Add stable startup/readiness integration checks around dev-env commands in CI environments with Docker.
|
|
55
|
+
4. Add bundling script(s) and SEA config proof-of-concept for a single-file executable artifact.
|