feat(deploy/windows): always deploy the plugin/script runner
`punktfunk-host plugins add/remove/list` forward to the bun runner, which on
Windows is resolved relative to the running exe (<exe-dir>\bun\bun.exe +
<exe-dir>\scripting\runner-cli.js). Only the installer ever laid that payload
down, so on a deploy-host.ps1 dev box — where the service runs out of
target\release — both paths are absent and the CLI bails with "the plugin
runner isn't installed". deploy-all.ps1 was host + web console only.
Add build-scripting.ps1: it mirrors CI (bun install --frozen-lockfile
--ignore-scripts + bun build src/runner-cli.ts --target=bun, gated on the same
`attempt=` sentinel that proves the dynamic plugin import stayed a runtime
import), then lays the bundle + scripting-run.cmd + the shared bun next to every
host exe it finds — the built one and whatever the PunktfunkHost service runs —
so it is correct on a dev checkout or an installed {app}. It stops a running
PunktfunkScripting first (a live runner holds bun.exe open) and does not
silently enable the opt-in task (-EnableTask to do so).
deploy-all.ps1 is now host -> web console -> runner, always, so the host binary
and the runner bundle never drift apart.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
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.
|
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
|
||||||
|
`<exe-dir>\scripting\runner-cli.js` + `scripting-run.cmd` with the bun runtime at `<exe-dir>\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
|
## Rebuild + redeploy everything
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell -ExecutionPolicy Bypass -File scripts\windows\deploy-all.ps1
|
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
|
Thin wrapper: runs `deploy-host.ps1`, `build-web.ps1` then `build-scripting.ps1` in sequence — the
|
||||||
fails, `deploy-host.ps1` rolls itself back and throws, which stops this script before the web
|
web console and plugin runner are **always** included, so the host binary and the runner bundle
|
||||||
console step runs.
|
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
|
## Typical flow after pulling new code
|
||||||
|
|
||||||
|
|||||||
@@ -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 - <exe-dir>\bun\bun.exe and
|
||||||
|
<exe-dir>\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 <exe-dir>\scripting\{runner-cli.js,scripting-run.cmd} + <exe-dir>\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."
|
||||||
@@ -1,26 +1,40 @@
|
|||||||
<#
|
<#
|
||||||
Rebuild + redeploy everything: the Windows host service AND the web management console.
|
Rebuild + redeploy everything: the Windows host service, the web management console AND the
|
||||||
Thin wrapper around deploy-host.ps1 + build-web.ps1 - see scripts\windows\README.md for what
|
plugin/script runner. Thin wrapper around deploy-host.ps1 + build-web.ps1 + build-scripting.ps1 -
|
||||||
each one does on its own (rollback behavior, build env, etc).
|
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
|
||||||
|
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
|
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'
|
$ErrorActionPreference = 'Stop'
|
||||||
$here = $PSScriptRoot
|
$here = $PSScriptRoot
|
||||||
|
|
||||||
Write-Host "=========================================="
|
Write-Host "=========================================="
|
||||||
Write-Host " 1/2 host service"
|
Write-Host " 1/3 host service"
|
||||||
Write-Host "=========================================="
|
Write-Host "=========================================="
|
||||||
& (Join-Path $here 'deploy-host.ps1')
|
& (Join-Path $here 'deploy-host.ps1')
|
||||||
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "=========================================="
|
Write-Host "=========================================="
|
||||||
Write-Host " 2/2 web console"
|
Write-Host " 2/3 web console"
|
||||||
Write-Host "=========================================="
|
Write-Host "=========================================="
|
||||||
& (Join-Path $here 'build-web.ps1')
|
& (Join-Path $here 'build-web.ps1')
|
||||||
|
|
||||||
Write-Host ""
|
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."
|
||||||
|
|||||||
Reference in New Issue
Block a user