diff --git a/.github/actions/wsl-run/.gitignore b/.github/actions/wsl-run/.gitignore new file mode 100644 index 000000000..a7fcc7788 --- /dev/null +++ b/.github/actions/wsl-run/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/.github/actions/wsl-run/action.yml b/.github/actions/wsl-run/action.yml new file mode 100644 index 000000000..d4446c54f --- /dev/null +++ b/.github/actions/wsl-run/action.yml @@ -0,0 +1,15 @@ +name: 'WSL Run Action' +description: 'Runs GitHub actions in WSL environment' +inputs: + uses: + description: 'The action to run in WSL' + required: false + with: + description: 'Input parameters for the action' + required: false + run: + description: 'Commands to run in WSL bash shell' + required: false +runs: + using: 'node20' + main: 'wsl-run-action.js' diff --git a/.github/actions/wsl-run/package-lock.json b/.github/actions/wsl-run/package-lock.json new file mode 100644 index 000000000..3ce947782 --- /dev/null +++ b/.github/actions/wsl-run/package-lock.json @@ -0,0 +1,101 @@ +{ + "name": "wsl-run-action", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "wsl-run-action", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@actions/core": "^1.10.1", + "@actions/exec": "^1.1.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@actions/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", + "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==", + "license": "MIT", + "dependencies": { + "@actions/exec": "^1.1.1", + "@actions/http-client": "^2.0.1" + } + }, + "node_modules/@actions/exec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", + "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", + "license": "MIT", + "dependencies": { + "@actions/io": "^1.0.1" + } + }, + "node_modules/@actions/http-client": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", + "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", + "license": "MIT", + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^5.25.4" + } + }, + "node_modules/@actions/io": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", + "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==", + "license": "MIT" + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/undici": { + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + } + } +} diff --git a/.github/actions/wsl-run/package.json b/.github/actions/wsl-run/package.json new file mode 100644 index 000000000..b6dd7ebe8 --- /dev/null +++ b/.github/actions/wsl-run/package.json @@ -0,0 +1,21 @@ +{ + "name": "wsl-run-action", + "version": "1.0.0", + "description": "GitHub action to run commands or other actions in WSL environment", + "main": "wsl-run-action.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "github", + "action", + "wsl" + ], + "author": "", + "license": "MIT", + "dependencies": { + "@actions/core": "^1.10.1", + "@actions/exec": "^1.1.1", + "js-yaml": "^4.1.0" + } +} diff --git a/.github/actions/wsl-run/wsl-run-action.js b/.github/actions/wsl-run/wsl-run-action.js new file mode 100644 index 000000000..58ef6b719 --- /dev/null +++ b/.github/actions/wsl-run/wsl-run-action.js @@ -0,0 +1,193 @@ +/** + * GitHub action to run other actions in WSL environment + * @param {string} uses - Name of GitHub action to run (e.g. 'actions/checkout@v4') + * @param {string} with - Input parameters to pass to the action in key=value format, supporting =, :, and = delimiters + * @param {string} run - Commands to run in WSL bash shell + * @return {Promise} Promise that resolves when action completes + */ +const core = require('@actions/core'); +const exec = require('@actions/exec'); +const yaml = require('js-yaml'); +const fs = require('fs'); +const path = require('path'); + +// Define WSL environment variables once at the top level +const baseWslEnv = [ + 'GITHUB_WORKSPACE/p', + 'GITHUB_ACTION', + 'GITHUB_ACTIONS', + 'GITHUB_ACTOR', + 'GITHUB_REPOSITORY', + 'GITHUB_EVENT_NAME', + 'GITHUB_EVENT_PATH/p', + 'GITHUB_SHA', + 'GITHUB_REF', + 'GITHUB_TOKEN', + 'GITHUB_RUN_ID', + 'GITHUB_RUN_NUMBER', + 'RUNNER_OS', + 'RUNNER_TEMP/p', + 'RUNNER_TOOL_CACHE/p', + 'CI', + 'GITHUB_DEBUG', + 'ACTIONS_RUNNER_DEBUG', + 'ACTIONS_STEP_DEBUG' +]; + +async function run() { + try { + // Get action inputs + const uses = core.getInput('uses'); + const withInputs = core.getInput('with'); + const runCommand = core.getInput('run'); + + // Validate inputs + if (runCommand && (uses || withInputs)) { + throw new Error('The "run" input cannot be used together with "uses" or "with" inputs'); + } + + if (!runCommand && !uses) { + throw new Error('Either "run" or "uses" input must be provided'); + } + + // If run command is provided, execute it directly in WSL + if (runCommand) { + core.info('Running command in WSL environment'); + core.debug(`Command: ${runCommand}`); + + // Set up basic environment variables for WSL + const wslEnv = baseWslEnv.join(':'); + + process.env.WSLENV = process.env.WSLENV ? `${process.env.WSLENV}:${wslEnv}` : wslEnv; + + await exec.exec('wsl.exe', ['bash', '-c', runCommand], { + env: process.env + }); + + core.info('Command completed successfully'); + return; + } + + // Original action execution logic for 'uses' + core.info(`Running action ${uses} in WSL environment`); + + // Parse action name and version + const [owner, repo, version] = uses.split(/[@/]/g); + core.debug(`Parsed action: owner=${owner}, repo=${repo}, version=${version}`); + + // Set up environment variables from with inputs + const env = {}; + if (withInputs) { + core.debug('Parsing with inputs:'); + core.debug(withInputs); + + // Parse key-value pairs with flexible delimiters + const inputs = withInputs + .split(/[\n,]/) + .map(line => line.trim()) + .filter(line => line.length > 0) + .reduce((acc, line) => { + const match = line.match(/^([^=:]+)(?:=|\s*:\s*|\s+=\s+)(.+)$/); + if (match) { + const [, key, value] = match; + acc[key.trim()] = value.trim(); + } + return acc; + }, {}); + + for (const [key, value] of Object.entries(inputs)) { + const envKey = `INPUT_${key.toUpperCase().replace(/-/g, '_')}`; + env[envKey] = value; + core.debug(`Setting env var: ${envKey}=${value}`); + } + } + + // Add environment variables to WSLENV + const wslEnv = [ + ...baseWslEnv, + ...Object.keys(env).map(key => key.slice(6)) + ].join(':'); + + process.env.WSLENV = process.env.WSLENV ? `${process.env.WSLENV}:${wslEnv}` : wslEnv; + core.debug(`Set WSLENV: ${process.env.WSLENV}`); + + // Clone and install the action in WSL + core.info('Cloning and installing action in WSL...'); + await exec.exec('wsl.exe', ['bash', '-c', ` + set -e + mkdir -p ~/actions/${owner}/${repo} + cd ~/actions/${owner}/${repo} + git clone --depth 1 --branch ${version} https://github.com/${owner}/${repo}.git . + # Install dependencies if needed + if [ -f "package.json" ]; then + npm install + npm run build || true + fi + # First check for dist/index.js + if [ -f "dist/index.js" ]; then + echo "MAIN_FILE=dist/index.js" >> $GITHUB_ENV + else + # Check for action files and notify JavaScript to parse them + for action_file in action.yml action.yaml; do + if [ -f "$action_file" ]; then + echo "ACTION_FILE=$action_file" >> $GITHUB_ENV + break + fi + done + fi + `], { + env: { + ...process.env, + ...env + } + }); + + // Parse action file if needed + if (!process.env.MAIN_FILE && process.env.ACTION_FILE) { + try { + const actionPath = path.join(process.env.HOME, 'actions', owner, repo, process.env.ACTION_FILE); + const actionConfig = yaml.load(fs.readFileSync(actionPath, 'utf8')); + + if (!actionConfig.runs?.main) { + throw new Error(`No 'main' field found in ${process.env.ACTION_FILE}`); + } + + process.env.MAIN_FILE = actionConfig.runs.main; + core.debug(`Found main entry point: ${process.env.MAIN_FILE}`); + } catch (error) { + core.error(`Failed to parse action file: ${error.message}`); + throw error; + } + } + + // Run the action + core.info('Running action...'); + const debugScript = process.env.GITHUB_DEBUG === 'true' ? 'env' : ''; + await exec.exec('wsl.exe', ['bash', '-c', ` + set -e + cd ~/actions/${owner}/${repo} + ${debugScript} + if [ -f "$MAIN_FILE" ]; then + node "$MAIN_FILE" + else + echo "Could not find entry point at $MAIN_FILE. Contents of directory:" + ls -R + exit 1 + fi + `], { + env: { + ...process.env, + ...env + } + }); + + core.info('Action completed successfully'); + + } catch (error) { + core.error('Action failed with error:'); + core.error(error); + core.setFailed(error.message); + } +} + +run(); diff --git a/.github/workflows/pr-setup-wsl-tests.yml b/.github/workflows/pr-setup-wsl-tests.yml new file mode 100644 index 000000000..c05ee8e58 --- /dev/null +++ b/.github/workflows/pr-setup-wsl-tests.yml @@ -0,0 +1,118 @@ +name: Setup WSL Tests + +on: + pull_request: + workflow_dispatch: + +jobs: + leia-tests: + runs-on: ${{ matrix.os }} + env: + TERM: xterm + strategy: + fail-fast: false + matrix: + leia-test: + - setup-linux + node-version: + - "20" + os: + - windows-2022 + + steps: + - name: Install Ubuntu for WSL2 + shell: pwsh + run: | + # Manually download and install Ubuntu to avoid the reboot that kills the Actions runner + Set-Service -Name StorSvc -StartupType Automatic + wsl.exe --set-default-version 2 + wsl.exe --update + $distributionInfo = Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/microsoft/WSL/refs/heads/master/distributions/DistributionInfo.json' + $downloadUrl = $distributionInfo.Distributions.Where({ $_.Name -eq 'Ubuntu' }).Amd64PackageUrl + $filename = [System.IO.Path]::GetFileName($downloadUrl) + Write-Output 'Installing Ubuntu for WSL2...' + Set-Location $env:TEMP + Invoke-WebRequest -Uri $downloadUrl -OutFile $filename + Expand-Archive -Path $filename -DestinationPath .\ + .\ubuntu.exe install --root + + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 1 + ref: ${{ github.sha }} + + - name: Setup WSL Run Action + shell: pwsh + run: | + cd .github/actions/wsl-run + npm install + # Install Node.js and git in WSL environment + wsl.exe bash -c "curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs git" + + - name: Install node ${{ matrix.node-version }} + uses: ./.github/actions/wsl-run + with: + uses: actions/setup-node@v4 + with: | + node-version: ${{ matrix.node-version }} + + - name: Bundle Deps + uses: ./.github/actions/wsl-run + with: + run: | + mkdir -p ~/prepare-release-action + git clone --depth 1 https://github.com/lando/prepare-release-action.git ~/prepare-release-action + cd ~/prepare-release-action + env \ + INPUT_LANDO-PLUGIN=true \ + INPUT_VERSION=dev \ + INPUT_SYNC=false \ + INPUT_BUNDLE-DEPENDENCIES=true \ + node dist/index.js + + - name: Install pkg dependencies + uses: ./.github/actions/wsl-run + with: + run: npm clean-install --prefer-offline --frozen-lockfile --production + + - name: Package into node binary + uses: ./.github/actions/wsl-run + id: pkg-action + with: + run: | + npm install -g @yao-pkg/pkg@5.16.1 + pkg bin/lando --target node${{ matrix.node-version }}-linux-x64 --options dns-result-order=ipv4first --output lando + echo "PKG_OUTPUT=lando" >> $GITHUB_ENV + + - name: Install full deps + uses: ./.github/actions/wsl-run + with: + run: npm clean-install --prefer-offline --frozen-lockfile + + - name: Setup lando ${{ steps.pkg-action.outputs.file }} + uses: ./.github/actions/wsl-run + with: + run: | + mkdir -p ~/setup-lando + git clone --depth 1 https://github.com/lando/setup-lando.git ~/setup-lando + cd ~/setup-lando + env \ + INPUT_AUTO-SETUP=false \ + INPUT_LANDO-VERSION="${{ steps.pkg-action.outputs.file }}" \ + INPUT_TELEMETRY=false \ + node dist/index.js + + - name: Run Leia Tests + uses: ./.github/actions/wsl-run + with: + run: | + mkdir -p ~/run-leia-action + git clone --depth 1 https://github.com/lando/run-leia-action.git ~/run-leia-action + cd ~/run-leia-action + env \ + INPUT_LEIA-TEST="./examples/${{ matrix.leia-test }}/README.md" \ + INPUT_CLEANUP-HEADER="Destroy tests" \ + INPUT_SHELL="bash" \ + INPUT_STDIN=true \ + node dist/index.js diff --git a/CHANGELOG.md b/CHANGELOG.md index ffe3118c8..21fd46ed5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ * Fixed bug causing failed update message when user needs to relaunch terminal * Improved `ux` for `autosetup` * Standardized non-TTY renderer selection to the `simple` renderer +* Added a WSL2 workflow for the `setup` tests ## v3.23.14 - [November 27, 2024](https://github.com/lando/core/releases/tag/v3.23.14)