๐Ÿ”ท Core

github-action-authoring

Authoring and debugging composite GitHub Actions for setup-boxlang. Use when: adding platform support (Windows/Linux/macOS), fixing PATH issues on Windows runners, updating action inputs/outputs/steps, writing PowerShell steps, debugging installer flag ordering, or adding CI test jobs to tests.yml.

$ npx skills add ortus-solutions/skills/github-action-authoring
$ coldbox ai skills install ortus-solutions/skills/github-action-authoring
๐Ÿ”— https://skills.boxlang.io/skills/raw/ortus-solutions/skills/github-action-authoring

GitHub Action Authoring โ€” setup-boxlang

When to Use

  • Adding or fixing Windows, Linux, or macOS support in action.yml
  • Writing or debugging PowerShell (shell: powershell) steps
  • Adding new inputs, outputs, or conditional steps
  • Updating tests.yml with new platform/feature coverage
  • Debugging PATH issues on GitHub Actions Windows runners
  • Installing third-party tools (BoxLang, CommandBox) in CI

Architecture

action.yml is a composite action with platform-specific parallel step pairs:

StepUnix/Linux/macOSWindows
Setup BoxLangshell: bash + install-boxlang.shshell: powershell + install-boxlang.ps1
Install CommandBoxshell: bash + curl + unzipshell: powershell + dedicated step
Install modulesshell: bash + install-bx-moduleskipped (not available on Windows)
ForgeBox API Keyshell: bashshell: powershell

Every step pair uses if: runner.os != 'Windows' / if: runner.os == 'Windows' conditions.


Critical Rules

Platform conditionals

Always use both sides for every platform-specific step:

- name: My Step (Unix/Linux/macOS)
  if: runner.os != 'Windows'
  shell: bash
  run: |
    ...

- name: My Step (Windows)
  if: runner.os == 'Windows'
  shell: powershell
  run: |
    ...

Environment variables in PowerShell steps

${{ env.SOME_VAR }} does NOT expand inside composite action PowerShell step bodies โ€” it resolves to an empty string. Always use $env:SOME_VAR to read runner environment variables at runtime:

# WRONG โ€” ${{ env.GITHUB_WORKSPACE }} expands to empty in PS steps
$dir = "${{ env.GITHUB_WORKSPACE }}\.boxlang"

# CORRECT
$dir = "$env:GITHUB_WORKSPACE\.boxlang"

${{ inputs.xxx }} and ${{ runner.os }} DO expand correctly (they are template substitutions, not env reads).

Writing to GITHUB_ENV and GITHUB_OUTPUT in PowerShell

# Set env var for subsequent steps
Add-Content -Path $env:GITHUB_ENV -Value "MY_VAR=value"

# Set step output
Add-Content -Path $env:GITHUB_OUTPUT -Value "my-output=value"

# Update PATH for subsequent steps
$env:PATH = "C:\my\bin;$env:PATH"
Add-Content -Path $env:GITHUB_ENV -Value "PATH=$env:PATH"

Invoke-WebRequest Content is byte[] in PS7+

On windows-latest (PowerShell 7), .Content from Invoke-WebRequest is System.Byte[], not a string. Invoke-Expression fails on it. Always save to a file first:

# WRONG โ€” fails on PS7+ runners
$script = Invoke-WebRequest -Uri $url -UseBasicParsing
Invoke-Expression $script.Content

# CORRECT
$tmpScript = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "script.ps1")
Invoke-WebRequest -Uri $url -OutFile $tmpScript -UseBasicParsing -ErrorAction Stop
& $tmpScript <arguments>
Remove-Item $tmpScript -Force -ErrorAction SilentlyContinue

Installer flag ordering matters

The install-boxlang.ps1 installer parses flags sequentially. --yes sets INSTALL_COMMANDBOX=true internally. If --without-commandbox comes before --yes, that later --yes overrides it. Always put --yes first:

# WRONG โ€” --yes overrides --without-commandbox
& $tmpScript --without-commandbox --yes

# CORRECT โ€” --without-commandbox processed last, wins
& $tmpScript --yes --without-commandbox

Windows PATH after installer

Third-party installers on Windows write to the Machine registry PATH (requires admin). Even if the registry write succeeds, the current PowerShell session does not pick it up automatically. Always explicitly rebuild PATH and push it to GITHUB_ENV:

# Explicitly prepend known install paths โ€” don't rely on the installer having done it
$env:PATH = "C:\known\install\bin;" +
            [System.Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" +
            [System.Environment]::GetEnvironmentVariable("PATH", "User")
Add-Content -Path $env:GITHUB_ENV -Value "PATH=$env:PATH"

Never delegate CommandBox installation to the BoxLang PS1 installer on Windows

The BoxLang installer places box.exe at c:\boxlang\bin\box.exe but its PATH update is unreliable on CI. Use the dedicated "Install CommandBox (Windows)" step instead, which:

  1. Downloads type/windows from Ortus (not type/bin โ€” that is the Linux binary)
  2. Extracts to a controlled path (C:\ProgramData\CommandBox)
  3. Verifies with the full path & "$boxDir\box.exe" version โ€” no PATH trust needed
  4. Explicitly prepends $boxDir to PATH + GITHUB_ENV

Composite action outputs when steps are conditional

Each platform step must have its own id. Outputs must pick the right step per platform:

outputs:
  boxlang-version:
    value: ${{ runner.os == 'Windows' && steps.install-boxlang-windows.outputs.version || steps.install-boxlang.outputs.version }}

BoxLang Installer Reference

PlatformInstaller URLVersion control
Unix/Linux/macOShttps://downloads.ortussolutions.com/ortussolutions/boxlang-quick-installer/install-boxlang.shPositional arg: ./install-boxlang.sh 1.2.0 --without-commandbox
Windowshttps://downloads.ortussolutions.com/ortussolutions/boxlang-quick-installer/install-boxlang.ps1$env:BOXLANG_TARGET_VERSION = "1.2.0" before calling script

Unix installer flags: <version> --without-commandbox Windows installer flags: --yes --without-commandbox (or --yes --with-commandbox)

CommandBox Download URLs

PlatformURL
Linux/macOShttps://www.ortussolutions.com/parent/download/commandbox/type/bin
Windowshttps://www.ortussolutions.com/parent/download/commandbox/type/windows
Specific versionhttps://downloads.ortussolutions.com/ortussolutions/commandbox/<version>/commandbox-bin-<version>.zip

Bash โ†’ PowerShell Conversion Quick Reference

BashPowerShell
if [[ -n "$VAR" ]]if ("$VAR" -ne "")
if [[ "$A" == "$B" ]]if ("$A" -eq "$B")
mkdir -p dirNew-Item -ItemType Directory -Path dir -Force \| Out-Null
echo "msg"Write-Host "msg"
echo "K=V" >> $GITHUB_ENVAdd-Content -Path $env:GITHUB_ENV -Value "K=V"
echo "k=v" >> $GITHUB_OUTPUTAdd-Content -Path $env:GITHUB_OUTPUT -Value "k=v"
curl -fsSL url -o fileInvoke-WebRequest -Uri url -OutFile file -UseBasicParsing
unzip file -d dirExpand-Archive -Path file -DestinationPath dir -Force
which cmd(Get-Command cmd -ErrorAction SilentlyContinue).Source
export VAR=val$env:VAR = "val"
exit 1exit 1
cmd 2>&1cmd 2>&1
$? (exit code)$LASTEXITCODE

tests.yml Conventions

  • Each new feature gets its own job (e.g. test_with_modules)
  • Each new platform gets its own named job (e.g. test_windows_default_home)
  • Platform matrix jobs use strategy.matrix.os and runs-on: ${{ matrix.os }}
  • Windows verification steps use shell: powershell and check env vars with $env:VAR
  • Unix verification steps use inline bash run: blocks
  • All Windows jobs verify BOXLANG_HOME is set and contains the expected path
# Windows env var verification pattern
- name: Verify BOXLANG_HOME (Windows)
  shell: powershell
  run: |
    if ($env:BOXLANG_HOME -ne "expected\path") {
      Write-Host "Expected: expected\path  Got: $env:BOXLANG_HOME"
      exit 1
    }
    Write-Host "BOXLANG_HOME is correct: $env:BOXLANG_HOME"