@automattic/vip 3.25.0 → 3.25.2-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.
@@ -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
+ }
@@ -36,4 +36,4 @@ const DEV_ENVIRONMENT_DEFAULTS = exports.DEV_ENVIRONMENT_DEFAULTS = {
36
36
  multisite: false,
37
37
  phpVersion: Object.keys(DEV_ENVIRONMENT_PHP_VERSIONS)[0]
38
38
  };
39
- const DEV_ENVIRONMENT_VERSION = exports.DEV_ENVIRONMENT_VERSION = '2.3.2';
39
+ const DEV_ENVIRONMENT_VERSION = exports.DEV_ENVIRONMENT_VERSION = '2.3.3';
@@ -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 = _formatters.default.formatData(data, {
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);
@@ -82,6 +131,15 @@ async function startEnvironment(lando, slug, options) {
82
131
  } else {
83
132
  await (0, _devEnvironmentLando.landoRebuild)(lando, instancePath);
84
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.`);
142
+ }
85
143
  await printEnvironmentInfo(lando, slug, {
86
144
  extended: false
87
145
  });
@@ -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', (0, _utils.dockerComposify)(slug)), {
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
  });
@@ -349,8 +407,8 @@ async function prepareLandoEnv(lando, instanceData, instancePath, integrationsCo
349
407
  ...instanceData,
350
408
  domain: lando.config.domain
351
409
  };
352
- const landoFile = await _ejs.default.renderFile(landoFileTemplatePath, templateData);
353
- const nginxFile = await _ejs.default.renderFile(nginxFileTemplatePath, templateData);
410
+ const landoFile = await renderTemplateFile(landoFileTemplatePath, landoTemplateAssetKey, templateData);
411
+ const nginxFile = await renderTemplateFile(nginxFileTemplatePath, nginxTemplateAssetKey, templateData);
354
412
  const instanceDataFile = JSON.stringify(instanceData);
355
413
  const landoFileTargetPath = _nodePath.default.join(instancePath, landoFileName);
356
414
  const landoOverridesFileTargetPath = _nodePath.default.join(instancePath, landoOverridesFileName);
@@ -18,10 +18,6 @@ exports.removeProxyCache = removeProxyCache;
18
18
  exports.validateDockerInstalled = validateDockerInstalled;
19
19
  var _chalk = _interopRequireDefault(require("chalk"));
20
20
  var _debug = _interopRequireDefault(require("debug"));
21
- var _bootstrap = require("lando/lib/bootstrap");
22
- var _lando = _interopRequireDefault(require("lando/lib/lando"));
23
- var _utils = _interopRequireDefault(require("lando/plugins/lando-core/lib/utils"));
24
- var _build = _interopRequireDefault(require("lando/plugins/lando-tooling/lib/build"));
25
21
  var _nodeChild_process = require("node:child_process");
26
22
  var _promises = require("node:dns/promises");
27
23
  var _promises2 = require("node:fs/promises");
@@ -31,6 +27,8 @@ var _nodeUtil = require("node:util");
31
27
  var _semver = require("semver");
32
28
  var _devEnvironmentCore = require("./dev-environment-core");
33
29
  var _dockerUtils = require("./docker-utils");
30
+ var _landoLoader = require("./lando-loader");
31
+ var _runtimeMode = require("../cli/runtime-mode");
34
32
  var _devEnvironment = require("../constants/dev-environment");
35
33
  var _env = _interopRequireDefault(require("../env"));
36
34
  var _userError = _interopRequireDefault(require("../user-error"));
@@ -43,6 +41,44 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
43
41
  const DEBUG_KEY = '@automattic/vip:bin:dev-environment';
44
42
  const debug = (0, _debug.default)(DEBUG_KEY);
45
43
  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
+ };
46
82
  const bannerLabelWidth = 18;
47
83
  let logPathRegistered = false;
48
84
  let resolvedLogPath = null;
@@ -146,7 +182,7 @@ const writeLogBanner = async config => {
146
182
  });
147
183
  const dockerVersions = await getDockerVersions(config);
148
184
  const command = process.argv.slice(1).join(' ');
149
- 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)), '===', '', ''];
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('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)), '===', '', ''];
150
186
  await (0, _promises2.writeFile)(logFilePath, bannerLines.join('\n'), {
151
187
  flag: 'a'
152
188
  });
@@ -157,7 +193,7 @@ const writeLogBanner = async config => {
157
193
  */
158
194
  async function getLandoConfig(options = {}) {
159
195
  // The path will be smth like `yarn/global/node_modules/lando/lib/lando.js`; we need the path up to `lando` (inclusive)
160
- const landoPath = (0, _nodePath.dirname)((0, _nodePath.dirname)(require.resolve('lando')));
196
+ const landoPath = (0, _nodePath.dirname)((0, _nodePath.dirname)((0, _landoLoader.resolveLandoModule)('lando')));
161
197
  debug(`Getting Lando config, using paths '${landoPath}' for plugins`);
162
198
  const isLandoDebugSelected = _debug.default.enabled(DEBUG_KEY);
163
199
  const isAllDebugSelected = _debug.default.enabled('"*"');
@@ -201,7 +237,7 @@ async function getLandoConfig(options = {}) {
201
237
  LANDO_HOST_GROUP_ID: process.platform === 'win32' ? '1000' : `${(0, _nodeOs.userInfo)().gid}`
202
238
  }
203
239
  };
204
- return (0, _bootstrap.buildConfig)(config);
240
+ return getLandoBuildConfig()(config);
205
241
  }
206
242
  const appMap = new Map();
207
243
  async function initLandoApplication(lando, instancePath) {
@@ -290,7 +326,8 @@ async function bootstrapLando(options = {}) {
290
326
  }
291
327
  registerLogPathOutput(config, Boolean(options.quiet));
292
328
  await writeLogBanner(config);
293
- const lando = new _lando.default(config);
329
+ const LandoClass = getLandoConstructor();
330
+ const lando = new LandoClass(config);
294
331
  _debug.default.log = (message, ...args) => {
295
332
  lando.log.debug(message, ...args);
296
333
  };
@@ -425,7 +462,7 @@ async function landoInfo(lando, instancePath, options = {}) {
425
462
  const started = new Date();
426
463
  try {
427
464
  const app = await getLandoApplication(lando, instancePath);
428
- const info = _utils.default.startTable(app);
465
+ const info = getLandoUtils().startTable(app);
429
466
  const reachableServices = app.info.filter(service => service.urls.length);
430
467
  reachableServices.forEach(service => info[`${service.service} urls`] = service.urls);
431
468
  const health = await checkEnvHealth(lando, app);
@@ -638,7 +675,7 @@ async function landoExec(lando, instancePath, toolName, args, options) {
638
675
  if (options.stdio) {
639
676
  tool.stdio = options.stdio;
640
677
  }
641
- const task = (0, _build.default)(tool, lando);
678
+ const task = getLandoBuildTask()(tool, lando);
642
679
  const argv = {
643
680
  // eslint-disable-next-line id-length
644
681
  _: 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.
@@ -0,0 +1,171 @@
1
+ # SEA Build and Signing Runbook
2
+
3
+ Purpose: build and sign the standalone VIP CLI executable (`dist/sea/vip` or `dist/sea/vip.exe`) for each supported platform.
4
+
5
+ This repo uses `helpers/build-sea.js` and the `npm run build:sea` script. SEA build is pinned to Node 22.
6
+
7
+ ## Shared Prerequisites
8
+
9
+ - Use Node 22.x exactly for SEA builds.
10
+ - Install dependencies before building: `npm ci`.
11
+ - Build from repo root.
12
+ - The build script embeds:
13
+ - Node runtime
14
+ - bundled CLI code (`src/bin/vip-sea.js`)
15
+ - SEA assets and runtime dependency archive (`sea.node_modules.tgz`)
16
+ - Output paths:
17
+ - Unix: `dist/sea/vip`
18
+ - Windows: `dist/sea/vip.exe`
19
+
20
+ ## Shared Build Steps
21
+
22
+ ```bash
23
+ npm ci
24
+ npm run build
25
+ npm run build:sea
26
+ ```
27
+
28
+ Quick smoke checks after every build:
29
+
30
+ ```bash
31
+ dist/sea/vip --version
32
+ dist/sea/vip whoami --help
33
+ dist/sea/vip dev-env info --help
34
+ ```
35
+
36
+ ## macOS (native)
37
+
38
+ Node/tool setup:
39
+
40
+ ```bash
41
+ export NVM_DIR="$HOME/.nvm"
42
+ . "$NVM_DIR/nvm.sh"
43
+ nvm use 22
44
+ node -v
45
+ ```
46
+
47
+ Build:
48
+
49
+ ```bash
50
+ npm ci
51
+ npm run build
52
+ npm run build:sea
53
+ ```
54
+
55
+ Notes:
56
+
57
+ - `helpers/build-sea.js` already does ad-hoc signing (`codesign --sign -`) after blob injection so local execution works.
58
+ - For distribution, replace ad-hoc signature with a real Developer ID certificate.
59
+
60
+ Distribution signing:
61
+
62
+ ```bash
63
+ codesign --remove-signature dist/sea/vip
64
+ codesign --sign "Developer ID Application: <TEAM/ORG>" --force --options runtime dist/sea/vip
65
+ codesign --verify --strict --verbose=2 dist/sea/vip
66
+ spctl -a -t exec -vv dist/sea/vip
67
+ ```
68
+
69
+ ## Linux (native)
70
+
71
+ Node/tool setup:
72
+
73
+ ```bash
74
+ node -v
75
+ ```
76
+
77
+ (Use Node 22 before build.)
78
+
79
+ Build:
80
+
81
+ ```bash
82
+ npm ci
83
+ npm run build
84
+ npm run build:sea
85
+ chmod +x dist/sea/vip
86
+ ```
87
+
88
+ Signing guidance:
89
+
90
+ - Linux does not have a universal OS-enforced Authenticode-style executable signature.
91
+ - Recommended: publish checksums and detached signatures.
92
+
93
+ Checksum + GPG example:
94
+
95
+ ```bash
96
+ sha256sum dist/sea/vip > dist/sea/vip.sha256
97
+ gpg --armor --detach-sign dist/sea/vip
98
+ ```
99
+
100
+ Cosign blob example:
101
+
102
+ ```bash
103
+ cosign sign-blob --yes --output-signature dist/sea/vip.sig dist/sea/vip
104
+ cosign verify-blob --signature dist/sea/vip.sig dist/sea/vip
105
+ ```
106
+
107
+ ## Windows (native)
108
+
109
+ Use PowerShell or `cmd.exe` on Windows (not WSL) when producing Windows artifacts.
110
+
111
+ Build:
112
+
113
+ ```powershell
114
+ npm ci
115
+ npm run build
116
+ npm run build:sea
117
+ .\dist\sea\vip.exe --version
118
+ ```
119
+
120
+ Authenticode signing (SignTool):
121
+
122
+ ```powershell
123
+ signtool sign /fd SHA256 /td SHA256 /tr http://timestamp.digicert.com /a .\dist\sea\vip.exe
124
+ signtool verify /pa /v .\dist\sea\vip.exe
125
+ ```
126
+
127
+ If your cert is in a PFX file:
128
+
129
+ ```powershell
130
+ signtool sign /f C:\path\cert.pfx /p <PFX_PASSWORD> /fd SHA256 /td SHA256 /tr http://timestamp.digicert.com .\dist\sea\vip.exe
131
+ ```
132
+
133
+ ## Windows from WSL
134
+
135
+ Important: WSL builds Linux binaries by default.
136
+
137
+ - If target is Linux binary: build/sign inside WSL using the Linux flow.
138
+ - If target is Windows `.exe`: run the build and signing commands in Windows context.
139
+
140
+ From WSL, invoke Windows PowerShell for a Windows-target build:
141
+
142
+ ```bash
143
+ WIN_REPO_PATH="$(wslpath -w "$PWD")"
144
+ powershell.exe -NoProfile -Command "Set-Location '$WIN_REPO_PATH'; npm ci; npm run build; npm run build:sea"
145
+ ```
146
+
147
+ Then sign in Windows context:
148
+
149
+ ```bash
150
+ powershell.exe -NoProfile -Command "Set-Location '$WIN_REPO_PATH'; signtool sign /fd SHA256 /td SHA256 /tr http://timestamp.digicert.com /a .\\dist\\sea\\vip.exe; signtool verify /pa /v .\\dist\\sea\\vip.exe"
151
+ ```
152
+
153
+ ## Release Checklist for Agents
154
+
155
+ - Confirm Node 22 before SEA build.
156
+ - Confirm artifact type matches target OS (`vip` vs `vip.exe`).
157
+ - Run smoke checks on the produced executable.
158
+ - Apply platform-appropriate signature method.
159
+ - Verify signature/checksum before publishing.
160
+ - Record signing method and timestamp authority in release notes.
161
+
162
+ ## GitHub Actions Automation
163
+
164
+ - Workflow: `.github/workflows/sea-build-sign.yml`
165
+ - Trigger: manual `workflow_dispatch`
166
+ - Jobs: native macOS/Linux/Windows SEA builds plus a Windows WSL SEA build
167
+ - Optional signing: set `sign_artifacts=true` and provide signing secrets/vars:
168
+ - macOS: `MACOS_CERTIFICATE_P12_BASE64`, `MACOS_CERTIFICATE_PASSWORD`, `MACOS_SIGNING_IDENTITY`
169
+ - Windows: `WINDOWS_CERTIFICATE_PFX_BASE64`, `WINDOWS_CERTIFICATE_PASSWORD`
170
+ - optional variable: `WINDOWS_TIMESTAMP_URL`
171
+ - Output: uploaded SEA binary + SHA256 artifact per job