diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index d5c1fecae..a94440844 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -29,7 +29,7 @@ "https-proxy-agent": "^7.0.6", "js-yaml": "^4.1.0", "jwt-decode": "4.0.0", - "lando": "github:automattic/lando-cli#bff037ea318948a1bac623c12ede017928cb99db", + "lando": "github:automattic/lando-cli#8f198e0f17dec1144efa216bec5a227dfc851900", "node-fetch": "^3.3.2", "node-stream-zip": "1.15.0", "open": "^11.0.0", @@ -2545,10 +2545,30 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -2566,6 +2586,7 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -2578,6 +2599,7 @@ "version": "6.2.3", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -2590,6 +2612,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -2607,6 +2630,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -2622,6 +2646,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -3670,6 +3695,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, "license": "MIT", "optional": true, "engines": { @@ -6762,6 +6788,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, "license": "MIT" }, "node_modules/ejs": { @@ -6803,6 +6830,7 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, "license": "MIT" }, "node_modules/enabled": { @@ -8079,30 +8107,6 @@ "integrity": "sha512-BUFj1aMubgib37I3v4q78fYo63Po7t4HUPTpQ6/QE6yK6cIQrP+W43FYToeTEyg5m2Y7eFUtijUuAv/PDlWuag==", "license": "MIT" }, - "node_modules/figlet": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.9.4.tgz", - "integrity": "sha512-uN6QE+TrzTAHC1IWTyrc4FfGo2KH/82J8Jl1tyKB7+z5DBit/m3D++Iu5lg91qJMnQQ3vpJrj5gxcK/pk4R9tQ==", - "license": "MIT", - "dependencies": { - "commander": "^14.0.0" - }, - "bin": { - "figlet": "bin/index.js" - }, - "engines": { - "node": ">= 17.0.0" - } - }, - "node_modules/figlet/node_modules/commander": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", - "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", - "license": "MIT", - "engines": { - "node": ">=20" - } - }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -8244,6 +8248,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", @@ -8260,6 +8265,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -9643,6 +9649,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -11297,8 +11304,8 @@ "node_modules/lando": { "name": "@lando/cli", "version": "3.6.5", - "resolved": "git+ssh://git@github.com/automattic/lando-cli.git#bff037ea318948a1bac623c12ede017928cb99db", - "integrity": "sha512-IR5BopOUjDDlKwu65d7oQ/SxGUFXVkAH4/ljKuT8I0lWbPE+bbTup3IcjMEiXz7pZWEAfIsBANHIhdnmXcF9NQ==", + "resolved": "git+ssh://git@github.com/automattic/lando-cli.git#8f198e0f17dec1144efa216bec5a227dfc851900", + "integrity": "sha512-2eM6b+AM2q1W6dP0x718x+X/ITLyXS8QXFiAEoF2wPx9HsKjf5ZhmD1haBRbjh7uDULq1BGKw2t657ojuUetpw==", "license": "GPL-3.0", "dependencies": { "axios": "^1.13.2", @@ -11307,19 +11314,16 @@ "cli-table3": "^0.6.5", "copy-dir": "^1.3.0", "dockerode": "^4.0.9", - "figlet": "^1.9.4", - "glob": "^10.5.0", + "glob": "^13.0.0", "js-yaml": "^4.1.1", "jsonfile": "^6.1.0", "lodash": "^4.17.21", "node-cache": "^5.1.2", - "object-hash": "^1.1.8", + "object-hash": "^3.0.0", "semver": "^7.7.3", "shelljs": "^0.10.0", "string-argv": "0.1.1", - "through": "^2.3.8", "transliteration": "^2.6.0", - "valid-url": "^1.0.9", "winston": "^3.11.0", "yargs": "^16.1.0" }, @@ -11391,20 +11395,38 @@ "license": "MIT" }, "node_modules/lando/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "license": "ISC", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", + "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", + "minimatch": "^10.1.1", "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "path-scurry": "^2.0.0" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/lando/node_modules/lru-cache": { + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/lando/node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -11414,11 +11436,25 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, + "node_modules/lando/node_modules/path-scurry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/lando/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -11746,6 +11782,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -12007,12 +12044,11 @@ "license": "MIT" }, "node_modules/object-hash": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", - "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==", - "license": "MIT", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "engines": { - "node": ">= 0.10.0" + "node": ">= 6" } }, "node_modules/object-inspect": { @@ -12295,6 +12331,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { @@ -12387,6 +12424,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -12403,12 +12441,14 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, "license": "ISC" }, "node_modules/path-scurry/node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -13719,6 +13759,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -13733,6 +13774,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/string-width/node_modules/emoji-regex": { @@ -13871,6 +13913,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -14092,12 +14135,6 @@ "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "license": "MIT" }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "license": "MIT" - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -14870,11 +14907,6 @@ "node": ">=10.12.0" } }, - "node_modules/valid-url": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", - "integrity": "sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA==" - }, "node_modules/vscode-json-languageservice": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-4.2.1.tgz", @@ -15189,6 +15221,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -15206,6 +15239,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -15221,6 +15255,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -15233,6 +15268,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, "node_modules/wrap-ansi/node_modules/ansi-regex": { diff --git a/package.json b/package.json index a59bf345e..6bd0950af 100644 --- a/package.json +++ b/package.json @@ -157,7 +157,7 @@ "https-proxy-agent": "^7.0.6", "js-yaml": "^4.1.0", "jwt-decode": "4.0.0", - "lando": "github:automattic/lando-cli#bff037ea318948a1bac623c12ede017928cb99db", + "lando": "github:automattic/lando-cli#459ac2d44ac3db17719493e879d52178410ff70e", "node-fetch": "^3.3.2", "node-stream-zip": "1.15.0", "open": "^11.0.0", diff --git a/src/bin/vip-dev-env-create.js b/src/bin/vip-dev-env-create.js index c5c9cbe4b..ef3621396 100755 --- a/src/bin/vip-dev-env-create.js +++ b/src/bin/vip-dev-env-create.js @@ -17,6 +17,7 @@ import { processStringOrBooleanOption, processSlug, ensureValidPathsInOptions, + getDevEnvLogFile, } from '../lib/dev-environment/dev-environment-cli'; import { getConfigurationFileOptions, @@ -102,7 +103,7 @@ cmd.argv( process.argv, async ( arg, opt ) => { slug = await getEnvironmentName( environmentNameOptions ); } - const lando = await bootstrapLando(); + const lando = await bootstrapLando( { logFile: getDevEnvLogFile( slug ) } ); validateDependencies( lando ); debug( 'Args: ', arg, 'Options: ', opt ); diff --git a/src/bin/vip-dev-env-destroy.js b/src/bin/vip-dev-env-destroy.js index 396f35fb4..45f604d42 100755 --- a/src/bin/vip-dev-env-destroy.js +++ b/src/bin/vip-dev-env-destroy.js @@ -10,6 +10,7 @@ import { handleCLIException, processSlug, validateDependencies, + getDevEnvLogFile, } from '../lib/dev-environment/dev-environment-cli'; import { destroyEnvironment } from '../lib/dev-environment/dev-environment-core'; import { bootstrapLando } from '../lib/dev-environment/dev-environment-lando'; @@ -50,7 +51,7 @@ command( { .argv( process.argv, async ( arg, opt ) => { const slug = await getEnvironmentName( opt ); - const lando = await bootstrapLando(); + const lando = await bootstrapLando( { logFile: getDevEnvLogFile( slug ) } ); validateDependencies( lando ); const trackingInfo = getEnvTrackingInfo( slug ); diff --git a/src/bin/vip-dev-env-exec.js b/src/bin/vip-dev-env-exec.js index 136d60b7d..deae0cb85 100755 --- a/src/bin/vip-dev-env-exec.js +++ b/src/bin/vip-dev-env-exec.js @@ -8,6 +8,7 @@ import { processBooleanOption, processSlug, validateDependencies, + getDevEnvLogFile, } from '../lib/dev-environment/dev-environment-cli'; import { exec, getEnvironmentPath } from '../lib/dev-environment/dev-environment-core'; import { bootstrapLando, isEnvUp } from '../lib/dev-environment/dev-environment-lando'; @@ -56,7 +57,7 @@ command( { .examples( examples ) .argv( process.argv, async ( unmatchedArgs, opt ) => { const slug = await getEnvironmentName( opt ); - const lando = await bootstrapLando(); + const lando = await bootstrapLando( { logFile: getDevEnvLogFile( slug ) } ); validateDependencies( lando ); const trackingInfo = getEnvTrackingInfo( slug ); diff --git a/src/bin/vip-dev-env-info.js b/src/bin/vip-dev-env-info.js index 17ef2f097..62736d259 100755 --- a/src/bin/vip-dev-env-info.js +++ b/src/bin/vip-dev-env-info.js @@ -9,6 +9,7 @@ import { handleCLIException, processSlug, validateDependencies, + getDevEnvLogFile, } from '../lib/dev-environment/dev-environment-cli'; import { printEnvironmentInfo, @@ -47,16 +48,16 @@ command( { .argv( process.argv, async ( arg, opt ) => { let trackingInfo; let slug; - const lando = await bootstrapLando(); if ( opt.all ) { trackingInfo = { all: true }; - slug = ''; + slug = undefined; } else { slug = await getEnvironmentName( opt ); trackingInfo = getEnvTrackingInfo( slug ); } + const lando = await bootstrapLando( { logFile: getDevEnvLogFile( slug ) } ); validateDependencies( lando ); await trackEvent( 'dev_env_info_command_execute', trackingInfo ); diff --git a/src/bin/vip-dev-env-list.js b/src/bin/vip-dev-env-list.js index 1522446b5..e0ef46dfd 100755 --- a/src/bin/vip-dev-env-list.js +++ b/src/bin/vip-dev-env-list.js @@ -4,6 +4,7 @@ import command from '../lib/cli/command'; import { handleCLIException, validateDependencies, + getDevEnvLogFile, } from '../lib/dev-environment/dev-environment-cli'; import { printAllEnvironmentsInfo } from '../lib/dev-environment/dev-environment-core'; import { bootstrapLando } from '../lib/dev-environment/dev-environment-lando'; @@ -24,7 +25,7 @@ command( { } ) .examples( examples ) .argv( process.argv, async () => { - const lando = await bootstrapLando(); + const lando = await bootstrapLando( { logFile: getDevEnvLogFile() } ); lando.events.constructor.prototype.setMaxListeners( 1024 ); validateDependencies( lando ); diff --git a/src/bin/vip-dev-env-logs.js b/src/bin/vip-dev-env-logs.js index 5bf7a34ed..7ebdf1a28 100755 --- a/src/bin/vip-dev-env-logs.js +++ b/src/bin/vip-dev-env-logs.js @@ -9,6 +9,7 @@ import { handleCLIException, processSlug, validateDependencies, + getDevEnvLogFile, } from '../lib/dev-environment/dev-environment-cli'; import { showLogs } from '../lib/dev-environment/dev-environment-core'; import { bootstrapLando } from '../lib/dev-environment/dev-environment-lando'; @@ -51,7 +52,7 @@ command( { .argv( process.argv, async ( arg, opt ) => { const slug = await getEnvironmentName( opt ); - const lando = await bootstrapLando(); + const lando = await bootstrapLando( { logFile: getDevEnvLogFile( slug ) } ); validateDependencies( lando ); const trackingInfo = getEnvTrackingInfo( slug ); diff --git a/src/bin/vip-dev-env-purge.js b/src/bin/vip-dev-env-purge.js index e3c012441..171ebe03c 100755 --- a/src/bin/vip-dev-env-purge.js +++ b/src/bin/vip-dev-env-purge.js @@ -15,6 +15,7 @@ import { handleCLIException, validateDependencies, promptForBoolean, + getDevEnvLogFile, } from '../lib/dev-environment/dev-environment-cli'; import { destroyEnvironment, @@ -57,7 +58,7 @@ command( { debug( 'Args: ', arg, 'Options: ', opt ); const allEnvNames = getAllEnvironmentNames(); - const lando = await bootstrapLando(); + const lando = await bootstrapLando( { logFile: getDevEnvLogFile() } ); if ( allEnvNames.length === 0 ) { console.log( 'No environments to purge!' ); diff --git a/src/bin/vip-dev-env-shell.js b/src/bin/vip-dev-env-shell.js index bb79a8689..eb0aa0dbd 100755 --- a/src/bin/vip-dev-env-shell.js +++ b/src/bin/vip-dev-env-shell.js @@ -9,6 +9,7 @@ import { getEnvironmentName, handleCLIException, processSlug, + getDevEnvLogFile, } from '../lib/dev-environment/dev-environment-cli'; import { getEnvironmentPath } from '../lib/dev-environment/dev-environment-core'; import { bootstrapLando, landoShell } from '../lib/dev-environment/dev-environment-lando'; @@ -90,7 +91,7 @@ command( { .argv( process.argv, async ( args, opt ) => { const slug = await getEnvironmentName( opt ); - const lando = await bootstrapLando(); + const lando = await bootstrapLando( { logFile: getDevEnvLogFile( slug ) } ); validateDependencies( lando ); const trackingInfo = getEnvTrackingInfo( slug ); diff --git a/src/bin/vip-dev-env-start.js b/src/bin/vip-dev-env-start.js index 699143a4f..476b37c0b 100755 --- a/src/bin/vip-dev-env-start.js +++ b/src/bin/vip-dev-env-start.js @@ -10,6 +10,7 @@ import { handleCLIException, postStart, processSlug, + getDevEnvLogFile, } from '../lib/dev-environment/dev-environment-cli'; import { startEnvironment } from '../lib/dev-environment/dev-environment-core'; import { bootstrapLando } from '../lib/dev-environment/dev-environment-lando'; @@ -76,7 +77,7 @@ command( { .examples( examples ) .argv( process.argv, async ( arg, opt ) => { const slug = await getEnvironmentName( opt ); - const lando = await bootstrapLando(); + const lando = await bootstrapLando( { logFile: getDevEnvLogFile( slug ) } ); validateDependencies( lando ); const startProcessing = new Date(); diff --git a/src/bin/vip-dev-env-stop.js b/src/bin/vip-dev-env-stop.js index 6f9ad9029..a5a6cbeb2 100755 --- a/src/bin/vip-dev-env-stop.js +++ b/src/bin/vip-dev-env-stop.js @@ -9,6 +9,7 @@ import { getEnvironmentName, processSlug, validateDependencies, + getDevEnvLogFile, } from '../lib/dev-environment/dev-environment-cli'; import { getAllEnvironmentNames, @@ -44,24 +45,28 @@ command( { .option( 'all', 'Stop all local environments.' ) .examples( examples ) .argv( process.argv, async ( arg, opt ) => { - const lando = await bootstrapLando(); - validateDependencies( lando ); - debug( 'Args: ', arg, 'Options: ', opt ); /** @type {Record< string, unknown >} */ let trackingInfo; /** @type {string[]} */ let environments; + /** @type {string|undefined} */ + let logSlug; if ( opt.all ) { trackingInfo = { all: true }; environments = getAllEnvironmentNames(); + logSlug = undefined; } else { const slug = await getEnvironmentName( opt ); trackingInfo = getEnvTrackingInfo( slug ); environments = [ slug ]; + logSlug = slug; } + const lando = await bootstrapLando( { logFile: getDevEnvLogFile( logSlug ) } ); + validateDependencies( lando ); + await trackEvent( 'dev_env_stop_command_execute', trackingInfo ); for ( const slug of environments ) { diff --git a/src/bin/vip-dev-env-sync-sql.js b/src/bin/vip-dev-env-sync-sql.js index dc06728ce..3b1bdeeaf 100755 --- a/src/bin/vip-dev-env-sync-sql.js +++ b/src/bin/vip-dev-env-sync-sql.js @@ -6,6 +6,7 @@ import { getEnvironmentName, processBooleanOption, processSlug, + getDevEnvLogFile, } from '../lib/dev-environment/dev-environment-cli'; import { bootstrapLando, isContainerRunning } from '../lib/dev-environment/dev-environment-lando'; import { parseLiveBackupCopyCLIOptions } from '../lib/live-backup-copy'; @@ -114,7 +115,7 @@ command( { } ); await trackerFn( 'execute' ); - const lando = await bootstrapLando(); + const lando = await bootstrapLando( { logFile: getDevEnvLogFile( slug ) } ); const isUp = ( await Promise.all( [ diff --git a/src/bin/vip-dev-env-update.js b/src/bin/vip-dev-env-update.js index 0ca13c50e..2c6085f83 100755 --- a/src/bin/vip-dev-env-update.js +++ b/src/bin/vip-dev-env-update.js @@ -17,6 +17,7 @@ import { promptForArguments, validateDependencies, ensureValidPathsInOptions, + getDevEnvLogFile, } from '../lib/dev-environment/dev-environment-cli'; import { getConfigurationFileOptions, @@ -68,7 +69,7 @@ cmd.examples( examples ); cmd.argv( process.argv, async ( arg, opt ) => { const slug = await getEnvironmentName( opt ); - const lando = await bootstrapLando(); + const lando = await bootstrapLando( { logFile: getDevEnvLogFile( slug ) } ); validateDependencies( lando ); const trackingInfo = getEnvTrackingInfo( slug ); diff --git a/src/commands/dev-env-import-sql.ts b/src/commands/dev-env-import-sql.ts index 23317fb3e..d27685cfb 100644 --- a/src/commands/dev-env-import-sql.ts +++ b/src/commands/dev-env-import-sql.ts @@ -6,6 +6,7 @@ import * as exit from '../lib/cli/exit'; import { getFileMeta, unzipFile } from '../lib/client-file-uploader'; import { getSqlDumpDetails, SqlDumpDetails, SqlDumpType } from '../lib/database'; import { + getDevEnvLogFile, processBooleanOption, validateDependencies, } from '../lib/dev-environment/dev-environment-cli'; @@ -39,7 +40,7 @@ export class DevEnvImportSQLCommand { ) {} public async run(): Promise< void > { - const lando = await bootstrapLando(); + const lando = await bootstrapLando( { logFile: getDevEnvLogFile( this.slug ) } ); validateDependencies( lando ); validateImportFileExtension( this.fileName ); diff --git a/src/lib/dev-environment/dev-environment-cli.ts b/src/lib/dev-environment/dev-environment-cli.ts index 69a0285f9..c9d3c97d6 100644 --- a/src/lib/dev-environment/dev-environment-cli.ts +++ b/src/lib/dev-environment/dev-environment-cli.ts @@ -954,6 +954,19 @@ export function processSlug( value: unknown ): string { return ( value ?? '' ).toString().toLowerCase(); } +export function formatDevEnvLogTimestamp( date: Date ): string { + return date.toISOString().replace( /\..*$/, '' ).replace( /[-:]/g, '' ).replace( 'T', '-' ); +} + +export function formatDevEnvLogSlug( slug: string ): string { + return slug.replace( /[^a-z0-9_-]+/gi, '-' ).toLowerCase(); +} + +export function getDevEnvLogFile( slug?: string ): string { + const slugPart = slug ? formatDevEnvLogSlug( slug ) : 'all'; + return `vip-dev-env-${ slugPart }-${ formatDevEnvLogTimestamp( new Date() ) }.log`; +} + declare function isNaN( value: unknown ): boolean; declare function parseFloat( value: unknown ): number; diff --git a/src/lib/dev-environment/dev-environment-lando.ts b/src/lib/dev-environment/dev-environment-lando.ts index 1dbdad690..aae3dcdca 100644 --- a/src/lib/dev-environment/dev-environment-lando.ts +++ b/src/lib/dev-environment/dev-environment-lando.ts @@ -6,10 +6,12 @@ import { buildConfig } from 'lando/lib/bootstrap'; import Lando, { type LandoConfig } from 'lando/lib/lando'; import landoUtils, { type AppInfo } from 'lando/plugins/lando-core/lib/utils'; import landoBuildTask from 'lando/plugins/lando-tooling/lib/build'; +import { execFile } from 'node:child_process'; import { lookup } from 'node:dns/promises'; -import { mkdir, rename, unlink } from 'node:fs/promises'; -import { userInfo } from 'node:os'; +import { mkdir, rename, unlink, stat, writeFile } from 'node:fs/promises'; +import { cpus, totalmem, userInfo } from 'node:os'; import path, { dirname } from 'node:path'; +import { promisify } from 'node:util'; import { satisfies } from 'semver'; import { @@ -21,6 +23,7 @@ import { } from './dev-environment-core'; import { getDockerSocket, getEngineConfig } from './docker-utils'; import { DEV_ENVIRONMENT_NOT_FOUND } from '../constants/dev-environment'; +import env from '../env'; import UserError from '../user-error'; import { xdgData } from '../xdg-data'; @@ -38,10 +41,179 @@ export interface LandoExecOptions { const DEBUG_KEY = '@automattic/vip:bin:dev-environment'; const debug = debugLib( DEBUG_KEY ); +interface LandoBootstrapOptions { + logFile?: string; + logName?: string; +} + +interface LandoConfigWithLogging extends Omit< LandoConfig, 'composeBin' | 'dockerBin' > { + logFile?: string; + logName?: string; + logDir?: string; + dockerBin?: string; + composeBin?: string; +} + +const execFileAsync = promisify( execFile ); +const bannerLabelWidth = 18; +let logPathRegistered = false; +let resolvedLogPath: string | null = null; + +const getLogFilePath = ( config: LandoConfigWithLogging ): string | null => { + if ( config.logFile ) { + return path.isAbsolute( config.logFile ) + ? config.logFile + : path.join( config.logDir ?? '', config.logFile ); + } + + if ( config.logDir && config.logName ) { + return path.join( config.logDir, `${ config.logName }.log` ); + } + + return null; +}; + +const registerLogPathOutput = ( config: LandoConfigWithLogging ): void => { + if ( logPathRegistered ) { + return; + } + + resolvedLogPath = getLogFilePath( config ); + if ( ! resolvedLogPath ) { + return; + } + + logPathRegistered = true; + process.once( 'exit', () => { + if ( resolvedLogPath && process.stdout.isTTY ) { + console.log( + `\n ${ formatBannerLine( + chalk.cyan( 'COMMAND LOG FILE'.padEnd( bannerLabelWidth ) ), + resolvedLogPath + ) }` + ); + } + } ); +}; + +const formatBytes = ( bytes: number ): string => { + const gb = bytes / 1024 ** 3; + return `${ gb.toFixed( 1 ) } GB`; +}; + +type DockerPluginInfo = { + Name?: string; + Version?: string; +}; + +type DockerClientInfo = { + Plugins?: DockerPluginInfo[]; +}; + +type DockerInfo = { + ServerVersion?: string; + ClientInfo?: DockerClientInfo; +}; + +const isRecord = ( value: unknown ): value is Record< string, unknown > => + typeof value === 'object' && value !== null; + +const isDockerPluginInfo = ( value: unknown ): value is DockerPluginInfo => isRecord( value ); + +const getDockerVersions = async ( config: LandoConfigWithLogging ) => { + const dockerBin = config.dockerBin ?? ''; + const composeBin = config.composeBin ?? ''; + let engine = 'unknown'; + let compose = 'unknown'; + let composePlugin = 'unknown'; + + try { + if ( dockerBin ) { + const { stdout } = await execFileAsync( dockerBin, [ 'info', '--format', 'json' ] ); + const dockerData = JSON.parse( stdout ) as DockerInfo; + if ( isRecord( dockerData ) ) { + const serverVersion = dockerData.ServerVersion; + if ( typeof serverVersion === 'string' ) { + engine = serverVersion; + } + + const clientInfo = dockerData.ClientInfo; + if ( isRecord( clientInfo ) ) { + const plugins = clientInfo.Plugins; + if ( Array.isArray( plugins ) ) { + const composePluginEntry = plugins.find( + plugin => isDockerPluginInfo( plugin ) && plugin.Name === 'compose' + ); + if ( composePluginEntry && typeof composePluginEntry.Version === 'string' ) { + composePlugin = composePluginEntry.Version; + } + } + } + } + } + } catch ( error ) { + debug( 'Failed to read docker version info: %O', error ); + } + + try { + if ( composeBin ) { + const { stdout } = await execFileAsync( composeBin, [ 'version', '--short' ] ); + compose = stdout.trim() || compose; + } + } catch ( error ) { + debug( 'Failed to read docker-compose version info: %O', error ); + } + + return { engine, compose, composePlugin }; +}; + +const formatBannerLine = ( label: string, value: string ): string => + `${ label.padEnd( bannerLabelWidth ) } ${ value }`; + +const writeLogBanner = async ( config: LandoConfigWithLogging ): Promise< void > => { + const logFilePath = getLogFilePath( config ); + if ( ! logFilePath ) { + return; + } + + try { + const existing = await stat( logFilePath ); + if ( existing.size > 0 ) { + return; + } + } catch { + // File does not exist yet. + } + + await mkdir( path.dirname( logFilePath ), { recursive: true } ); + + const dockerVersions = await getDockerVersions( config ); + const command = process.argv.slice( 1 ).join( ' ' ); + const bannerLines = [ + '=== VIP Dev Env Log ===', + formatBannerLine( 'COMMAND', command ), + formatBannerLine( 'OS', `${ env.os.name } ${ env.os.version } ${ env.os.arch }` ), + formatBannerLine( 'NODE', env.node.version ), + formatBannerLine( 'VIP-CLI', env.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( totalmem() ) ), + formatBannerLine( 'CPU', String( cpus().length ) ), + '===', + '', + '', + ]; + + await writeFile( logFilePath, bannerLines.join( '\n' ), { flag: 'a' } ); +}; + /** * @return {Promise} Lando configuration */ -async function getLandoConfig(): Promise< LandoConfig > { +async function getLandoConfig( options: LandoBootstrapOptions = {} ): Promise< LandoConfig > { // The path will be smth like `yarn/global/node_modules/lando/lib/lando.js`; we need the path up to `lando` (inclusive) const landoPath = dirname( dirname( require.resolve( 'lando' ) ) ); @@ -61,6 +233,7 @@ async function getLandoConfig(): Promise< LandoConfig > { const vipDir = path.join( xdgData(), 'vip' ); const landoDir = path.join( vipDir, 'lando' ); const fakeHomeDir = path.join( landoDir, 'home' ); + const logDir = path.join( landoDir, 'logs' ); try { await mkdir( fakeHomeDir, { recursive: true } ); @@ -70,6 +243,9 @@ async function getLandoConfig(): Promise< LandoConfig > { const config = { logLevelConsole, + logDir, + logFile: options.logFile ?? 'vip-dev-env.log', + logName: options.logName ?? 'vip-dev-env', configSources: [ path.join( landoDir, 'config.yml' ) ], landoFile: '.lando.yml', preLandoFiles: [ '.lando.base.yml', '.lando.dist.yml', '.lando.upstream.yml' ], @@ -187,11 +363,15 @@ async function getLandoApplication( lando: Lando, instancePath: string ): Promis } } -export async function bootstrapLando(): Promise< Lando > { +export async function bootstrapLando( options: LandoBootstrapOptions = {} ): Promise< Lando > { const started = new Date(); try { + if ( process.env.DEBUG && ! process.env.DEBUG.includes( '-winston:*' ) ) { + process.env.DEBUG = `${ process.env.DEBUG },-winston:*`; + } + const socket = await getDockerSocket(); - const config = await getLandoConfig(); + const config = await getLandoConfig( options ); debug( 'Docker socket: %j', socket ); @@ -200,7 +380,13 @@ export async function bootstrapLando(): Promise< Lando > { debug( 'Engine config: %j', config.engineConfig ); } + registerLogPathOutput( config as LandoConfigWithLogging ); + await writeLogBanner( config as LandoConfigWithLogging ); + const lando = new Lando( config ); + debugLib.log = ( message: string, ...args: unknown[] ) => { + lando.log.debug( message, ...args ); + }; lando.events.once( 'pre-engine-build', async ( data: App ) => { const instanceData = readEnvironmentData( data.name ); diff --git a/types/lando/lib/logger.d.ts b/types/lando/lib/logger.d.ts index 32bdd8a66..108a27cc1 100644 --- a/types/lando/lib/logger.d.ts +++ b/types/lando/lib/logger.d.ts @@ -3,15 +3,20 @@ export = Log; declare class Log extends winston.Logger { constructor( { logDir, + logFile, logLevelConsole, logLevel, logName, + logger, }?: { logDir: any; + logFile?: string; logLevelConsole?: string; logLevel?: string; logName?: string; + logger?: winston.Logger; } ); sanitizedKeys: string[]; alsoSanitize( key: any ): void; + child( logName: string ): Log; }