diff --git a/scripts/windows/README.md b/scripts/windows/README.md index a7c20574..4d9c5c77 100644 --- a/scripts/windows/README.md +++ b/scripts/windows/README.md @@ -55,15 +55,40 @@ powershell -ExecutionPolicy Bypass -File scripts\windows\build-web.ps1 this to iterate on the console against an installed host - `punktfunk-host.exe web setup` (or a fresh install) is what creates the task in the first place. +## Plugin/script runner + +```powershell +powershell -ExecutionPolicy Bypass -File scripts\windows\build-scripting.ps1 +powershell -ExecutionPolicy Bypass -File scripts\windows\build-scripting.ps1 -EnableTask +``` + +`bun install && bun build src/runner-cli.ts --target=bun` in `sdk\` -> one self-contained +`runner-cli.js` (effect + the SDK inlined; the operator's plugin `import()` stays a runtime import, +gated on the same `attempt=` check CI and the `.deb` builder use), then lays it out as +`\scripting\runner-cli.js` + `scripting-run.cmd` with the bun runtime at `\bun\bun.exe`. + +**That layout is load-bearing.** `punktfunk-host plugins add/remove/list` forwards package ops to the +runner, and on Windows it resolves the runner *relative to the running exe* (`crates\punktfunk-host\src\plugins.rs`). +Since `deploy-host.ps1` runs the service out of `target\release`, a bundle sitting only in the +installed `{app}` leaves the freshly built exe reporting *"the plugin runner isn't installed"*. The +script deploys next to **every** host exe it finds - the built one and whatever the `PunktfunkHost` +service actually runs. + +The `PunktfunkScripting` task is registered **disabled** (opt-in) by the installer, so the script +stages the bundle but does not silently enable it. Pass `-EnableTask` on a box you are validating +plugins on (equivalent to `punktfunk-host plugins enable`). + ## Rebuild + redeploy everything ```powershell powershell -ExecutionPolicy Bypass -File scripts\windows\deploy-all.ps1 +powershell -ExecutionPolicy Bypass -File scripts\windows\deploy-all.ps1 -EnableScriptingTask ``` -Thin wrapper: runs `deploy-host.ps1` then `build-web.ps1` in sequence. If the host build/start -fails, `deploy-host.ps1` rolls itself back and throws, which stops this script before the web -console step runs. +Thin wrapper: runs `deploy-host.ps1`, `build-web.ps1` then `build-scripting.ps1` in sequence — the +web console and plugin runner are **always** included, so the host binary and the runner bundle +never drift apart. If the host build/start fails, `deploy-host.ps1` rolls itself back and throws, +which stops this script before the later steps run. ## Typical flow after pulling new code diff --git a/scripts/windows/build-scripting.ps1 b/scripts/windows/build-scripting.ps1 new file mode 100644 index 00000000..57fa94ab --- /dev/null +++ b/scripts/windows/build-scripting.ps1 @@ -0,0 +1,145 @@ +<# + Rebuild the plugin/script runner bundle from the CURRENT sdk/ source and lay it out next to the + host exe, so `punktfunk-host plugins ...` works and the PunktfunkScripting task runs new code. + + powershell -ExecutionPolicy Bypass -File scripts\windows\build-scripting.ps1 + powershell -ExecutionPolicy Bypass -File scripts\windows\build-scripting.ps1 -EnableTask + + WHY the layout matters: the plugins CLI forwards package ops (add/remove/list) to the runner, and + on Windows it resolves the runner RELATIVE TO THE RUNNING EXE - \bun\bun.exe and + \scripting\runner-cli.js (crates\punktfunk-host\src\plugins.rs). deploy-host.ps1 runs the + service out of target\release, so a bundle sitting only in the installed {app} leaves the freshly + built exe reporting "the plugin runner isn't installed". We therefore deploy next to EVERY host exe + we can find: the built one, and whatever the PunktfunkHost service actually runs. + + The PunktfunkScripting task ships DISABLED (opt-in). We do not silently enable it - pass + -EnableTask on a dev box you are validating plugins on. +#> +param( + [switch]$EnableTask # enable + restart PunktfunkScripting (opt-in) +) +$ErrorActionPreference = 'Stop' +$repo = Split-Path (Split-Path $PSScriptRoot) # scripts\windows -> repo root +$sdk = Join-Path $repo 'sdk' +$task = 'PunktfunkScripting' + +# bun is both the bundler AND the runtime the runner ships on. Honor $env:BUN_EXE (what CI sets) +# before falling back to the dev box's portable copy - the same one build-web.ps1 uses. +$bun = $env:BUN_EXE +if (-not ($bun -and (Test-Path $bun))) { $bun = 'C:\Users\Public\bun\bin\bun.exe' } +if (-not (Test-Path $bun)) { throw "bun not found at $bun (set `$env:BUN_EXE to override)" } + +Write-Host "== punktfunk plugin/script runner deploy ==" +Write-Host "bun : $bun" + +# --- 1. build the self-contained bundle ------------------------------------------------------ +# --target=bun inlines effect + the SDK into ONE js; the operator's plugin import stays a runtime +# import. --ignore-scripts skips the `prepare` codegen (it wants ../api/openapi.json, not needed). +$stage = Join-Path $repo 'target\scripting' +New-Item -ItemType Directory -Force -Path $stage | Out-Null +$bundle = Join-Path $stage 'runner-cli.js' + +Push-Location $sdk +try { + Write-Host "bun install ..." + & $bun install --frozen-lockfile --ignore-scripts + if ($LASTEXITCODE -ne 0) { throw "sdk bun install failed (exit $LASTEXITCODE)" } + Write-Host "bun build src/runner-cli.ts -> $bundle" + & $bun build src/runner-cli.ts --target=bun "--outfile=$bundle" + if ($LASTEXITCODE -ne 0) { throw "runner bundle build failed (exit $LASTEXITCODE)" } +} finally { Pop-Location } + +# Same gate CI and the .deb builder use: 'attempt=' only survives when the dynamic plugin import was +# bundled as a RUNTIME import. If it is missing the bundle would load but never run a plugin. +if (-not (Select-String -Path $bundle -Pattern 'attempt=' -Quiet)) { + throw "runner bundle missing the dynamic plugin import - wrong build" +} +Write-Host "bundle : OK ($([math]::Round((Get-Item $bundle).Length / 1KB)) KB)" + +# --- 2. work out every host-exe dir that needs the payload ------------------------------------ +$targets = New-Object System.Collections.Generic.List[string] +function Add-Target([string]$exe) { + if (-not $exe) { return } + $dir = Split-Path $exe + if ($dir -and (Test-Path $dir) -and -not ($targets -contains $dir)) { $targets.Add($dir) | Out-Null } +} + +# a) the binary deploy-host.ps1 just built - what you invoke by hand to test the new CLI. +Add-Target (Join-Path $repo 'target\release\punktfunk-host.exe') + +# b) whatever the service actually runs (an installed {app} on a box set up via setup.exe). The +# binPath carries args and may be quoted, so pull the .exe out with a regex. +$qc = & sc.exe qc 'PunktfunkHost' 2>$null +if ($qc) { + $line = $qc | Select-String 'BINARY_PATH_NAME' | Select-Object -First 1 + if ($line -and "$line" -match '([A-Za-z]:\\[^"]*?punktfunk-host\.exe)') { Add-Target $Matches[1] } +} + +if ($targets.Count -eq 0) { throw "no punktfunk-host.exe found - run deploy-host.ps1 first." } + +# --- 3. lay out \scripting\{runner-cli.js,scripting-run.cmd} + \bun\bun.exe --- +# Stop a LIVE runner first: it holds bun.exe (and the bundle) open, so copying over them fails with a +# sharing violation. Step 4 restarts it. Reap stragglers by command line the way build-web.ps1 does - +# schtasks /end does not always take the child bun with it. +$existing = Get-ScheduledTask -TaskName $task -ErrorAction SilentlyContinue +if ($existing -and $existing.State -eq 'Running') { + Write-Host "stopping $task (holds bun.exe open) ..." + & schtasks /end /tn $task 2>$null | Out-Null + Get-CimInstance Win32_Process -Filter "Name='bun.exe'" -ErrorAction SilentlyContinue | + Where-Object { $_.CommandLine -match 'runner-cli\.js' } | + ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue } + Start-Sleep 2 +} + +foreach ($dir in $targets) { + Write-Host "" + Write-Host "deploying -> $dir" + $scrDir = Join-Path $dir 'scripting' + $bunDir = Join-Path $dir 'bun' + New-Item -ItemType Directory -Force -Path $scrDir, $bunDir | Out-Null + + Copy-Item $bundle (Join-Path $scrDir 'runner-cli.js') -Force + Copy-Item (Join-Path $PSScriptRoot 'scripting-run.cmd') (Join-Path $scrDir 'scripting-run.cmd') -Force + # The runner import()s the operator's .ts plugins, so bun ships WITH it rather than being assumed + # on PATH - exactly what the installer does. Skip the copy when it is already the same build. + $bunDst = Join-Path $bunDir 'bun.exe' + if (-not (Test-Path $bunDst) -or (Get-Item $bunDst).Length -ne (Get-Item $bun).Length) { + Copy-Item $bun $bunDst -Force + } + Write-Host " scripting\runner-cli.js, scripting\scripting-run.cmd, bun\bun.exe" +} + +# --- 4. the opt-in scheduled task ------------------------------------------------------------- +# Registered DISABLED by the installer; the runner is inert until there is automation to run. We only +# bounce it when it already exists, and only enable it when explicitly asked. ($existing was captured +# in step 3, BEFORE we stopped a running instance - so State still reflects how we found the box.) +Write-Host "" +if (-not $existing) { + Write-Host "note : the $task task is not registered on this box (installer registers it)." + Write-Host " The plugins CLI still works - it runs the runner directly." +} +elseif ($EnableTask) { + if ($existing.State -eq 'Disabled') { + Enable-ScheduledTask -TaskName $task | Out-Null + Write-Host "task : $task ENABLED (-EnableTask)" + } + & schtasks /end /tn $task 2>$null | Out-Null + Start-Sleep 2 + & schtasks /run /tn $task | Out-Null + Write-Host "task : $task restarted on the new bundle" +} +elseif ($existing.State -eq 'Disabled') { + Write-Host "note : $task is DISABLED (opt-in, as shipped) - the new bundle is staged but no" + Write-Host " runner is supervising plugins. Enable it for on-glass validation with:" + Write-Host " powershell -File scripts\windows\build-scripting.ps1 -EnableTask" + Write-Host " (or: punktfunk-host plugins enable)" +} +else { + & schtasks /end /tn $task 2>$null | Out-Null + Start-Sleep 2 + & schtasks /run /tn $task | Out-Null + Write-Host "task : $task restarted on the new bundle" +} + +Write-Host "" +Write-Host "DONE - plugin/script runner deployed." diff --git a/scripts/windows/deploy-all.ps1 b/scripts/windows/deploy-all.ps1 index e6fa9f5c..88fe1184 100644 --- a/scripts/windows/deploy-all.ps1 +++ b/scripts/windows/deploy-all.ps1 @@ -1,26 +1,40 @@ <# - Rebuild + redeploy everything: the Windows host service AND the web management console. - Thin wrapper around deploy-host.ps1 + build-web.ps1 - see scripts\windows\README.md for what - each one does on its own (rollback behavior, build env, etc). + Rebuild + redeploy everything: the Windows host service, the web management console AND the + plugin/script runner. Thin wrapper around deploy-host.ps1 + build-web.ps1 + build-scripting.ps1 - + see scripts\windows\README.md for what each one does on its own (rollback behavior, build env, etc). powershell -ExecutionPolicy Bypass -File scripts\windows\deploy-all.ps1 + powershell -ExecutionPolicy Bypass -File scripts\windows\deploy-all.ps1 -EnableScriptingTask + + All three modules are ALWAYS deployed - a host binary whose runner bundle is stale (or missing) + fails `punktfunk-host plugins add` outright, since the CLI forwards package ops to the runner it + finds next to the exe. Run from an elevated PowerShell. deploy-host.ps1 throws (and rolls itself back) on a failed - build/start, which stops this script before the web console step runs. + build/start, which stops this script before the later steps run. #> +param( + [switch]$EnableScriptingTask # forwarded to build-scripting.ps1 (opt-in task) +) $ErrorActionPreference = 'Stop' $here = $PSScriptRoot Write-Host "==========================================" -Write-Host " 1/2 host service" +Write-Host " 1/3 host service" Write-Host "==========================================" & (Join-Path $here 'deploy-host.ps1') Write-Host "" Write-Host "==========================================" -Write-Host " 2/2 web console" +Write-Host " 2/3 web console" Write-Host "==========================================" & (Join-Path $here 'build-web.ps1') Write-Host "" -Write-Host "DONE - host + web console redeployed." +Write-Host "==========================================" +Write-Host " 3/3 plugin/script runner" +Write-Host "==========================================" +& (Join-Path $here 'build-scripting.ps1') -EnableTask:$EnableScriptingTask + +Write-Host "" +Write-Host "DONE - host + web console + plugin runner redeployed."