From 76cee5bc6de67e951b73e58a7d0b7e8dfb9e1c51 Mon Sep 17 00:00:00 2001 From: Aaron Feledy Date: Mon, 9 Dec 2024 18:10:11 -0600 Subject: [PATCH] create standalone action for wsl steps --- .github/actions/wsl-run/.gitignore | 1 + .github/actions/wsl-run/action.yml | 15 ++ .github/actions/wsl-run/package.json | 20 +++ .github/actions/wsl-run/wsl-run-action.js | 176 ++++++++++++++++++++++ .github/workflows/pr-setup-wsl-tests.yml | 128 +++++++++------- 5 files changed, 283 insertions(+), 57 deletions(-) create mode 100644 .github/actions/wsl-run/.gitignore create mode 100644 .github/actions/wsl-run/action.yml create mode 100644 .github/actions/wsl-run/package.json create mode 100644 .github/actions/wsl-run/wsl-run-action.js 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.json b/.github/actions/wsl-run/package.json new file mode 100644 index 000000000..c3d5fc6aa --- /dev/null +++ b/.github/actions/wsl-run/package.json @@ -0,0 +1,20 @@ +{ + "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" + } +} 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..45952b808 --- /dev/null +++ b/.github/actions/wsl-run/wsl-run-action.js @@ -0,0 +1,176 @@ +/** + * 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'); + +// 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 + # Fall back to checking action.yml for entry point + for action_file in action.yml action.yaml; do + if [ -f "$action_file" ]; then + MAIN_FILE=$(grep "main:" "$action_file" | sed 's/main:[[:space:]]*//;s/^["'\'']*//;s/["'\''].*$//') + if [ ! -z "$MAIN_FILE" ]; then + echo "Entry point from $action_file: $MAIN_FILE" + echo "MAIN_FILE=$MAIN_FILE" >> $GITHUB_ENV + break + fi + fi + done + fi + `], { + env: { + ...process.env, + ...env + } + }); + + // 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 index 440392b64..c05ee8e58 100644 --- a/.github/workflows/pr-setup-wsl-tests.yml +++ b/.github/workflows/pr-setup-wsl-tests.yml @@ -18,10 +18,6 @@ jobs: - "20" os: - windows-2022 - defaults: - run: - # Actions passes commands as a script with windows line endings, so we need to convert them to unix line endings - shell: wsl.exe bash --noprofile --norc -euo pipefail -c "touch ~/.env && source ~/.env; GITHUB_TOKEN=${{ github.token }} RUNNER_DEBUG=${{ env.RUNNER_DEBUG }} exec $(script="$(wslpath '{0}')" && sed -i 's/\r$//' "$script" && echo "$script")" steps: - name: Install Ubuntu for WSL2 @@ -39,66 +35,84 @@ jobs: Invoke-WebRequest -Uri $downloadUrl -OutFile $filename Expand-Archive -Path $filename -DestinationPath .\ .\ubuntu.exe install --root - - name: Setup Ubuntu Shell Environment - shell: pwsh - run: | - $envVars = @" - export CI=true - export GITHUB_ACTIONS=true - export GITHUB_WORKSPACE=$(wsl.exe wslpath -a "$env:GITHUB_WORKSPACE") - export RUNNER_OS=Linux - export RUNNER_TEMP=$(wsl.exe wslpath -a "$env:RUNNER_TEMP") - export RUNNER_TOOL_CACHE=$(wsl.exe wslpath -a "$env:RUNNER_TOOL_CACHE") - "@ - wsl.exe bash -c "echo '$envVars' > ~/.env" + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 1 + ref: ${{ github.sha }} + + - name: Setup WSL Run Action + shell: pwsh run: | - git clone --depth 1 --single-branch https://github.com/${{ github.repository }}.git . - git fetch --depth 1 origin ${{ github.sha }} - git checkout ${{ github.sha }} + 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 }} - run: | - curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash - echo 'export NVM_DIR="$HOME/.nvm"' >> ~/.env - echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> ~/.env - source ~/.env - nvm install ${{ matrix.node-version }} + uses: ./.github/actions/wsl-run + with: + uses: actions/setup-node@v4 + with: | + node-version: ${{ matrix.node-version }} + - name: Bundle Deps - 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 - export INPUT_PLUGIN=true - export INPUT_VERSION=dev - export INPUT_SYNC=false - export INPUT_BUNDLE-DEPENDENCIES=true - node dist/index.js + 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 - run: npm clean-install --prefer-offline --frozen-lockfile --production + 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 - 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 }} + 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 - run: npm clean-install --prefer-offline --frozen-lockfile + uses: ./.github/actions/wsl-run + with: + run: npm clean-install --prefer-offline --frozen-lockfile + - name: Setup lando ${{ steps.pkg-action.outputs.file }} - run: | - mkdir -p ~/setup-lando - git clone --depth 1 https://github.com/lando/setup-lando.git ~/setup-lando - cd ~/setup-lando - export INPUT_AUTO_SETUP=false - export INPUT_LANDO_VERSION="${{ steps.pkg-action.outputs.file }}" - export INPUT_TELEMETRY=false - node dist/index.js + 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 - 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 - export INPUT_LEIA_TEST="./examples/${{ matrix.leia-test }}/README.md" - export INPUT_CLEANUP_HEADER="Destroy tests" - export INPUT_SHELL="bash" - export INPUT_STDIN=true - node dist/index.js + 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