Files
software-mansion__argent/.github/workflows/windows-e2e.yml
Hubert Gancarczyk 1a7e97fb14 feat(flows): run a local script in a bounded child process (#864)
Adds the executor a flow `script:` step runs on: a fresh Node child per
step,
a protocol over its IPC channel, and the two watchdogs that make a hung
or
runaway script the runner's problem rather than the host's.

The step itself is not here. This branch stops at the executor and its
unit
tests; `feat/flow-script-step` stacks the YAML directive, the runner
integration, and the reference docs on top.

## What it does

- **One child per step.** Spawned with an old-space heap limit, an
explicit
working directory, and an environment built from an allowlist rather
than
  copied from the tool server.
- **A deadline and a lifeline.** A separate watchdog holds the deadline,
so a
script that wedges the event loop still dies on time; a second one reaps
the
whole process tree when the parent goes away, so a grandchild cannot
outlive
  the run.
- **A concurrency queue.** Slots are bounded per server. A step that
never gets
one is refused with a message that says so, and an aborted run frees its
slot
  immediately.
- **Log budgets.** 64 KiB per step and 256 KiB per run, counted on the
bytes
the report keeps: redaction and V8 frame collapsing both run before
anything
is counted, so what the limits bound is the size of the report rather
than
  the size of the script's writes.
- **Secret hold-back.** The scrub walks a chunk and stops where a value
could
still begin, so neither half of a value split across two chunks is
released
  on its own, and a shorter value is never taken where the longer one
  containing it has not arrived yet.
- **A failure taxonomy.** Twelve kinds, split into what the script did
(it
threw, it did not load, it exited non-zero, it wrote an unusable
`output`)
and what the host did to it (a limit, a signal, a spawn that failed, a
queue
  slot it never got).

## Docs

`packages/docs/docs/reference/configuration.mdx` lists the two
configuration
keys this branch adds, `scripts.maxTimeoutMs` and `scripts.heapLimitMb`.
The
`script:` step itself is documented with the step, on
`feat/flow-script-step`.

## Verification

`npm run build`, `npx eslint . --max-warnings 0`, `npx prettier --check
.`,
`npm run knip`, `npm run typecheck:scripts`, the test typecheck across
every
workspace, the tool-server suite (4719 passed, 1 skipped) and `npm run
test:scripts` (92 tests) are green, as is `npx docusaurus build`. The
executor's
own behaviour is covered by the eight `test/flows/script/` files added
here,
against real child processes, and the compiled `dist/` executor was
driven
against its copied runner assets over every path the review reached.

Each review fix carries the run that reproduced it before the change and
the
mutation that proves the new test fails without it: the prefix-secret
leak and
all three npm `node-options` routes were reproduced end to end through
the
executor against real child processes, and the five coverage findings
were
confirmed by re-applying the exact mutation each thread named.




<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added reliable execution of trusted flow scripts with concurrency
control, cancellation, timeouts, memory limits, logging, and detailed
failure reporting.
* Added safeguards for process cleanup, watchdog termination, output
validation, and secret redaction.
* Added global configuration for maximum script runtime and memory
usage.
* **Bug Fixes**
* Improved handling of script failures, stalled processes, malformed
output, and child-process termination.
* **Documentation**
  * Documented script resource limits and global configuration behavior.
* **Chores**
* Updated builds and packaging to include required flow-script runtime
assets.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Hubert Gancarczyk <claude-hubert.gancarczyk@swmansion.com>
2026-09-07 11:34:30 +02:00

191 lines
8.9 KiB
YAML

name: Windows E2E
# Proves Argent runs on Windows. iOS Simulator is macOS-only (Apple), so
# "Argent on Windows" means the Android + Chromium host control plane.
#
# This job, windows-chromium, has the tool-server drive a real headless Chrome
# over CDP (discover → screenshot → describe → tap → observe the DOM change).
# Chromium control is pure host-side TypeScript, so a hosted Windows runner (no
# virtualization) verifies it fully. It also runs the cross-platform resolver
# unit tests natively on Windows and confirms adb.exe resolution.
#
# The simulator-server.exe itself (the binary the Android path spawns) lives in
# the private radon repo, so it can't be built here without a cross-repo token.
# Its Windows build + smoke-run is verified in radon CI instead — see the
# build_argent_windows job in radon's simulator-server-ci workflow (companion
# PR). WHPX is available on windows-latest (Server 2025), so an Android emulator
# does boot on a hosted runner — that is where the boot-device stdio fix was
# verified. The full Android emulator E2E still lives in radon CI alongside that
# binary rather than here, because this repo can't build simulator-server.exe
# without a cross-repo token; that path is therefore out of scope for this job.
on:
workflow_dispatch:
pull_request:
branches:
- main
paths:
- "packages/tool-server/src/utils/command-on-path.ts"
- "packages/tool-server/src/utils/android-binary.ts"
- "packages/tool-server/src/utils/adb.ts"
- "packages/tool-server/src/utils/check-deps.ts"
- "packages/tool-server/src/utils/vega-cli.ts"
- "packages/tool-server/src/utils/chromium-discovery.ts"
- "packages/tool-server/src/tools/simulator/stop-metro.ts"
- "packages/tool-server/src/tools/devices/boot-device.ts"
- "packages/tool-server/src/utils/device-shutdown.ts"
# The unit tests this job runs — editing one must re-run it here.
- "packages/tool-server/test/command-on-path.test.ts"
- "packages/tool-server/test/android-binary-windows.test.ts"
- "packages/tool-server/test/adb-resolve-avd-path.test.ts"
- "packages/tool-server/test/check-deps.test.ts"
- "packages/tool-server/test/http-dep-gate.test.ts"
- "packages/tool-server/test/stop-metro-netstat.test.ts"
- "packages/tool-server/test/device-shutdown.test.ts"
- "packages/native-devtools-ios/src/index.ts"
- "packages/native-devtools-ios/test/index.test.ts"
- "packages/argent/scripts/bundle-tools.cjs"
- "packages/argent/scripts/argent-simulator-server.cjs"
- "scripts/dev.cjs"
- "scripts/download-simulator-server.sh"
- "scripts/ci/windows-chromium-e2e.mjs"
- "scripts/ci/e2e-chromium-page.html"
- ".github/workflows/windows-e2e.yml"
jobs:
windows-chromium:
name: Chromium control plane + resolver unit tests (Windows)
runs-on: windows-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install dependencies
run: npm ci
- name: Fix rollup native bindings (win32)
# vitest 4 transforms via rollup, whose platform binary is an optional
# dep npm ci sometimes skips; install the win32 one explicitly.
run: npm install @rollup/rollup-win32-x64-msvc --no-save
- name: Build workspace
run: npm run build
- name: Resolver unit tests on Windows (tool-server)
# The win32 branches added for Windows support, exercised on a real
# Windows host (real `where.exe`, real `.exe` access semantics) rather
# than a mocked platform. The broader suite has POSIX-only fixtures, so
# this is the curated cross-platform subset.
working-directory: packages/tool-server
run: >
npx vitest run
test/command-on-path.test.ts
test/android-binary-windows.test.ts
test/adb-resolve-avd-path.test.ts
test/check-deps.test.ts
test/http-dep-gate.test.ts
test/stop-metro-netstat.test.ts
test/device-shutdown.test.ts
- name: Resolver unit tests on Windows (native-devtools-ios)
working-directory: packages/native-devtools-ios
run: npx vitest run
- name: adb.exe resolves on Windows
# windows-latest ships the Android SDK with ANDROID_HOME/ANDROID_SDK_ROOT
# set but does NOT put platform-tools on PATH — exactly the gap the
# Windows resolver closes (`.exe` suffix + SDK-root fallback). Resolve
# adb the way the tool-server does, then run the *resolved* path (a bare
# `adb` wouldn't be found, which is the whole point).
shell: pwsh
run: |
$adb = node -e "require('./packages/tool-server/dist/utils/android-binary.js').resolveAndroidBinary('adb').then(p => process.stdout.write(p || ''))"
Write-Host "resolved adb -> $adb"
if ([string]::IsNullOrWhiteSpace($adb) -or -not $adb.ToLower().EndsWith('adb.exe')) {
Write-Error "expected resolveAndroidBinary('adb') to return an adb.exe path"
exit 1
}
if (-not (Test-Path $adb)) { Write-Error "resolved adb path does not exist: $adb"; exit 1 }
& $adb version
- name: Locate Chrome
id: chrome
run: |
$candidates = @(
"$env:ProgramFiles\Google\Chrome\Application\chrome.exe",
"${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe",
"$env:LOCALAPPDATA\Google\Chrome\Application\chrome.exe"
)
$chrome = $candidates | Where-Object { Test-Path $_ } | Select-Object -First 1
if (-not $chrome) { Write-Error "Google Chrome not found on this runner"; exit 1 }
Write-Host "Chrome: $chrome"
"path=$chrome" >> $env:GITHUB_OUTPUT
# Launch Chrome + the tool-server AND run the E2E in one step. On Windows
# runners, `Start-Process` background processes don't reliably survive into
# a later step (unlike `nohup` on Linux), so both must stay in scope for
# the duration of the E2E — hence a single step rather than three.
- name: Launch Chrome + tool-server and run Chromium E2E
env:
ARGENT_E2E_URL: http://127.0.0.1:3033
run: |
$page = "file:///" + ((Resolve-Path scripts/ci/e2e-chromium-page.html).Path -replace '\\','/')
$udd = Join-Path $env:RUNNER_TEMP "chrome-e2e-profile"
$chromeLog = Join-Path $env:RUNNER_TEMP "chrome.log"
Start-Process -FilePath "${{ steps.chrome.outputs.path }}" -ArgumentList @(
"--headless=new","--remote-debugging-port=9222","--user-data-dir=$udd",
"--no-first-run","--no-default-browser-check","--disable-gpu",$page
) -RedirectStandardOutput $chromeLog -RedirectStandardError "$chromeLog.err" | Out-Null
$cdpUp = $false
for ($i = 0; $i -lt 30; $i++) {
try {
$r = Invoke-WebRequest -UseBasicParsing "http://127.0.0.1:9222/json/version" -TimeoutSec 2
if ($r.StatusCode -eq 200) { Write-Host "CDP up at t+${i}s"; $cdpUp = $true; break }
} catch {}
Start-Sleep -Seconds 1
}
if (-not $cdpUp) { Get-Content "$chromeLog.err" -ErrorAction SilentlyContinue; Write-Error "Chrome CDP never came up on :9222"; exit 1 }
$env:ARGENT_PORT = "3033"
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.argent" | Out-Null
$tsLog = Join-Path $env:RUNNER_TEMP "tool-server.log"
Start-Process -FilePath "node" -ArgumentList @("packages/tool-server/dist/index.js","start") `
-RedirectStandardOutput $tsLog -RedirectStandardError "$tsLog.err" | Out-Null
$tsUp = $false
for ($i = 0; $i -lt 40; $i++) {
try {
$r = Invoke-WebRequest -UseBasicParsing "http://127.0.0.1:3033/tools" -TimeoutSec 2
if ($r.StatusCode -eq 200) { Write-Host "tool-server up at t+${i}s"; $tsUp = $true; break }
} catch {}
Start-Sleep -Seconds 1
}
if (-not $tsUp) {
Write-Host "--- tool-server.log ---"; Get-Content $tsLog -ErrorAction SilentlyContinue
Write-Host "--- tool-server.err ---"; Get-Content "$tsLog.err" -ErrorAction SilentlyContinue
Write-Error "tool-server never came up on :3033"; exit 1
}
$env:ARGENT_E2E_OUT = Join-Path $env:RUNNER_TEMP "e2e"
node scripts/ci/windows-chromium-e2e.mjs
- name: Upload artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: windows-chromium-e2e
path: |
${{ runner.temp }}/e2e/*.png
${{ runner.temp }}/tool-server.log
${{ runner.temp }}/tool-server.log.err
${{ runner.temp }}/chrome.log
${{ runner.temp }}/chrome.log.err
if-no-files-found: warn
retention-days: 7