Skip to content

fix: preserve PowerShell session during install #34

fix: preserve PowerShell session during install

fix: preserve PowerShell session during install #34

name: Phase acceptance
on:
push:
pull_request:
workflow_dispatch:
inputs:
release_tag:
description: Optional Contexa CLI prerelease tag to verify
required: false
type: string
jobs:
installer-locale:
name: Installer locale / ${{ matrix.os }}
runs-on: ${{ matrix.os }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: Validate installer syntax (POSIX)
if: runner.os != 'Windows'
shell: bash
run: sh -n install.sh
- name: Validate installer syntax (PowerShell 5.1)
if: runner.os == 'Windows'
shell: powershell
run: |
$tokens = $null
$errors = $null
[System.Management.Automation.Language.Parser]::ParseFile(
(Resolve-Path '.\install.ps1'), [ref]$tokens, [ref]$errors) | Out-Null
if ($errors.Count) { throw ($errors | Out-String) }
- name: Validate installer syntax (PowerShell 7)
if: runner.os == 'Windows'
shell: pwsh
run: |
$tokens = $null
$errors = $null
[System.Management.Automation.Language.Parser]::ParseFile(
(Resolve-Path '.\install.ps1'), [ref]$tokens, [ref]$errors) | Out-Null
if ($errors.Count) { throw ($errors | Out-String) }
- name: Run installer fault and locale acceptance
env:
CONTEXA_LIFECYCLE_REPEATS: '20'
CONTEXA_FAULT_REPEATS: '5'
run: >-
node -e "const f=require('fs'),c=require('child_process');const r=c.spawnSync(process.execPath,['--test','test/installer-runtime.test.js'],{encoding:'utf8',windowsHide:true});const out=(r.stdout||'')+(r.stderr||'');f.writeFileSync('installer-runtime.tap',out);process.stdout.write(out);process.exit(r.status===null?1:r.status)"
- name: Run the full installer fault matrix with PowerShell 7
if: runner.os == 'Windows'
env:
CONTEXA_TEST_POWERSHELL: pwsh.exe
CONTEXA_LIFECYCLE_REPEATS: '20'
CONTEXA_FAULT_REPEATS: '5'
run: >-
node -e "const f=require('fs'),c=require('child_process');const r=c.spawnSync(process.execPath,['--test','test/installer-runtime.test.js'],{encoding:'utf8',windowsHide:true});const out=(r.stdout||'')+(r.stderr||'');f.writeFileSync('installer-runtime-pwsh.tap',out);process.stdout.write(out);process.exit(r.status===null?1:r.status)"
- name: Run installer source and release-contract acceptance
run: node --test test/installers.test.js
- name: Publish installer test failure detail
if: failure()
shell: bash
run: |
node -e "const f=require('fs');const paths=['installer-runtime.tap','installer-runtime-pwsh.tap'];const s=paths.map(p=>f.existsSync(p)?p+'\n'+f.readFileSync(p,'utf8'):'').join('\n').slice(-12000)||'installer TAP output is missing';const e=s.replace(/%/g,'%25').replace(/\r/g,'%0D').replace(/\n/g,'%0A');console.log('::error file=test/installer-runtime.test.js,line=1,title=Installer runtime failure::'+e)"
- uses: actions/upload-artifact@v4
if: always()
with:
name: installer-runtime-evidence-${{ matrix.os }}
path: |
installer-runtime.tap
installer-runtime-pwsh.tap
- name: Resolve current and previous prerelease tags
env:
INPUT_RELEASE_TAG: ${{ inputs.release_tag }}
GITHUB_TOKEN: ${{ github.token }}
shell: bash
run: |
node <<'NODE'
const fs = require('fs');
(async () => {
const requested = (process.env.INPUT_RELEASE_TAG || '').trim();
const response = await fetch('https://api.github.com/repos/contexa-security/contexa-cli/releases?per_page=30', {
headers: {
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
'User-Agent': 'install-ctxa-phase-acceptance',
},
});
if (!response.ok) throw new Error(`release lookup failed: ${response.status}`);
const releases = (await response.json()).filter(release => release.prerelease && !release.draft);
const current = requested
? releases.find(release => release.tag_name === requested)
: releases[0];
if (!current) throw new Error(`requested prerelease not found: ${requested || 'latest'}`);
const currentIndex = releases.findIndex(release => release.tag_name === current.tag_name);
const previous = releases.slice(currentIndex + 1)
.find(release => release.tag_name !== current.tag_name);
if (!previous) throw new Error(`previous prerelease not found for ${current.tag_name}`);
fs.appendFileSync(process.env.GITHUB_ENV,
`CONTEXA_PHASE_TAG=${current.tag_name}\nCONTEXA_PREVIOUS_TAG=${previous.tag_name}\n`);
})().catch(error => {
console.error(error);
process.exitCode = 1;
});
NODE
- name: Verify actual install, update, no-op, rollback and init/reset (POSIX)
if: runner.os != 'Windows'
shell: bash
env:
CONTEXA_INSTALL_DIR: ${{ runner.temp }}/contexa-bin
CONTEXA_SKIP_PATH_UPDATE: '1'
run: |
set -euo pipefail
test "$(id -u)" -ne 0
expected_current="${CONTEXA_PHASE_TAG#v}"
expected_previous="${CONTEXA_PREVIOUS_TAG#v}"
CONTEXA_VERSION="$CONTEXA_PREVIOUS_TAG" sh ./install.sh
test "$("$CONTEXA_INSTALL_DIR/contexa" --version)" = "$expected_previous"
CONTEXA_VERSION="$CONTEXA_PHASE_TAG" sh ./install.sh
test "$("$CONTEXA_INSTALL_DIR/contexa" --version)" = "$expected_current"
test "$("$CONTEXA_INSTALL_DIR/contexa.previous" --version)" = "$expected_previous"
CONTEXA_VERSION="$CONTEXA_PHASE_TAG" sh ./install.sh
CONTEXA_INSTALL_ACTION=rollback sh ./install.sh
test "$("$CONTEXA_INSTALL_DIR/contexa" --version)" = "$expected_previous"
CONTEXA_VERSION="$CONTEXA_PHASE_TAG" sh ./install.sh
fixture="$RUNNER_TEMP/contexa-installer-smoke"
mkdir -p "$fixture/src/main/java/example" "$fixture/src/main/resources"
printf '%s\n' "plugins { id 'org.springframework.boot' version '3.3.0' }" \
'repositories { mavenCentral() }' \
"dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' }" \
> "$fixture/build.gradle"
printf "%s\n" "rootProject.name = 'installer-smoke'" > "$fixture/settings.gradle"
printf '%s\n' 'server:' ' port: 9080' > "$fixture/src/main/resources/application.yml"
"$CONTEXA_INSTALL_DIR/contexa" init --yes --dir "$fixture"
"$CONTEXA_INSTALL_DIR/contexa" reset --yes --dir "$fixture"
test ! -f "$fixture/contexa/manifest.json"
CONTEXA_INSTALL_ACTION=uninstall sh ./install.sh
test ! -e "$CONTEXA_INSTALL_DIR/contexa"
test ! -e "$CONTEXA_INSTALL_DIR/contexa.previous"
- name: Verify actual install, update, no-op, rollback and init/reset (Windows)
if: runner.os == 'Windows'
shell: powershell
env:
CONTEXA_INSTALL_DIR: ${{ runner.temp }}\contexa-bin
CONTEXA_SKIP_PATH_UPDATE: '1'
run: |
$ErrorActionPreference = 'Stop'
trap {
$message = $_.Exception.ToString().Replace('%', '%25').Replace("`r", '%0D').Replace("`n", '%0A')
Write-Output ('::error file=.github/workflows/phase-acceptance.yml,line=132,title=Windows actual installer failure::' + $message)
exit 1
}
$binary = Join-Path $env:CONTEXA_INSTALL_DIR 'contexa.exe'
$env:CONTEXA_VERSION = $env:CONTEXA_PREVIOUS_TAG
& .\install.ps1
if ((& $binary --version).Trim() -ne $env:CONTEXA_PREVIOUS_TAG.TrimStart('v')) { throw 'previous install mismatch' }
$env:CONTEXA_VERSION = $env:CONTEXA_PHASE_TAG
& .\install.ps1
if ((& $binary --version).Trim() -ne $env:CONTEXA_PHASE_TAG.TrimStart('v')) { throw 'current update mismatch' }
& .\install.ps1
$env:CONTEXA_INSTALL_ACTION = 'rollback'
& .\install.ps1
Remove-Item Env:CONTEXA_INSTALL_ACTION
if ((& $binary --version).Trim() -ne $env:CONTEXA_PREVIOUS_TAG.TrimStart('v')) { throw 'rollback mismatch' }
$env:CONTEXA_VERSION = $env:CONTEXA_PHASE_TAG
& .\install.ps1
$fixture = Join-Path $env:RUNNER_TEMP 'contexa-installer-smoke'
New-Item -ItemType Directory -Force -Path (Join-Path $fixture 'src\main\resources') | Out-Null
New-Item -ItemType Directory -Force -Path (Join-Path $fixture 'src\main\java\example') | Out-Null
$utf8 = New-Object System.Text.UTF8Encoding($false)
[IO.File]::WriteAllText((Join-Path $fixture 'build.gradle'), (@(
"plugins { id 'org.springframework.boot' version '3.3.0' }",
'repositories { mavenCentral() }',
"dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' }"
) -join "`n") + "`n", $utf8)
[IO.File]::WriteAllText((Join-Path $fixture 'settings.gradle'), "rootProject.name = 'installer-smoke'`n", $utf8)
[IO.File]::WriteAllText((Join-Path $fixture 'src\main\resources\application.yml'), "server:`n port: 9080`n", $utf8)
& $binary init --yes --dir $fixture
& $binary reset --yes --dir $fixture
if (Test-Path -LiteralPath (Join-Path $fixture 'contexa\manifest.json')) { throw 'reset left manifest behind' }
$env:CONTEXA_INSTALL_ACTION = 'uninstall'
& .\install.ps1
if (Test-Path -LiteralPath $binary) { throw 'uninstall left the final binary behind' }
if (Test-Path -LiteralPath ($binary + '.previous')) { throw 'uninstall left the previous binary behind' }