import { assert, assertEquals, assertRejects, assertStringIncludes } from "@std/assert"; import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context"; import { mkdir, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { defineExtension } from "@berry/api"; import { localEnvironment } from "@berry/api/environment.ts"; import { rootedEnvironment, testExtension } from "@berry/api/testing.ts"; import { tempDir } from "@berry/util"; import code from "./mod.ts"; import { powershellCommand } from "./commands.ts"; Deno.test("PowerShell registers only in Windows conversations, never globally", async () => { await using root = await tempDir(); const registered: string[] = []; const ext = testExtension( defineExtension({ ...code, setup(api) { const contribute = code.setup({ ...api, tool(tool) { assert(tool.name !== "powershell"); api.tool(tool); }, }); return (conv) => contribute?.({ ...conv, tool(tool) { registered.push(tool.name); conv.tool(tool); }, }); }, }), { env: rootedEnvironment(root.path) }, ); const probe = async (platform: string) => { registered.length = 0; await assertRejects( () => ext.call("absent", {}, { env: { ...ext.env, platform } }), Error, "No tool named absent", ); return registered.includes("powershell"); }; assertEquals(await probe("linux"), false); assertEquals(await probe("win32"), true); assertEquals(await probe("linux"), false); }); Deno.test("PowerShell follows the target environment and prefers PS7 before falling back to PS5", async () => { await using root = await tempDir(); for ( const dir of [ "bin dir", "legacy", "program files/PowerShell/7", "windows/System32/WindowsPowerShell/v1.0", ] ) { await mkdir(join(root.path, dir), { recursive: true }); } const binaries = [ "bin dir/pwsh.EXE", "program files/PowerShell/7/pwsh.exe", "legacy/powershell.EXE", "windows/System32/WindowsPowerShell/v1.0/powershell.exe", ]; for (const file of binaries) await writeFile(join(root.path, file), ""); let invocation: string | readonly string[] = ""; const env = { ...rootedEnvironment(root.path, { variables: { pAtH: '"/bin dir";/legacy', PaThExT: ".EXE", ProgramFiles: "/program files", SystemRoot: "/windows", }, }), platform: "win32", exec: (command: string | readonly string[]) => { invocation = command; return Promise.resolve({ ok: true as const, value: { exitCode: 0 } }); }, }; const ext = testExtension(code, { env }); await ext.call("powershell", { command: "Get-Location", timeout: 10 }); assert(Array.isArray(invocation)); assertEquals(invocation[0], "/bin dir/pwsh.EXE"); await Deno.remove(join(root.path, binaries[0])); await ext.call("powershell", { command: "Get-Location", timeout: 10 }); assertEquals(invocation[0], "/program files/PowerShell/7/pwsh.exe"); await Deno.remove(join(root.path, binaries[1])); await ext.call("powershell", { command: "Get-Location", timeout: 10 }); assertEquals(invocation[0], "/legacy/powershell.EXE"); await Deno.remove(join(root.path, binaries[2])); await ext.call("powershell", { command: "Get-Location", timeout: 10 }); assertEquals(invocation[0], "/windows/System32/WindowsPowerShell/v1.0/powershell.exe"); await Deno.remove(join(root.path, binaries[3])); await assertRejects( () => ext.call("powershell", { command: "Get-Location", timeout: 10 }), Error, "PowerShell not found", ); await assertRejects( () => ext.call("powershell", { command: "Get-Location", timeout: 10 }, { env: { ...env, platform: "linux" }, }), Error, "No tool named powershell", ); }); let hasPowerShell = true; try { await powershellCommand(localEnvironment(Deno.cwd()), ""); } catch { hasPowerShell = false; } Deno.test({ name: "PowerShell runs literal Unicode scripts, preserves native failures, and spills UTF-8 output", ignore: !hasPowerShell, async fn() { await using root = await tempDir(); const env = localEnvironment(root.path); const run = async (command: string, timeout = 10) => { let output = ""; const result = await env.exec(await powershellCommand(env, command), { timeout, onOutput: (chunk) => output += chunk, spill: { afterBytes: 50 * 1024, afterLines: 2000 }, }, BACKGROUND_CONTEXT); assert(result.ok, result.ok ? "" : result.error.message); return { ...result.value, output }; }; const success = await run(`'héllo 🐈 "quotes" \\ backslash'; (Get-Location).Path`); assertEquals(success.exitCode, 0); assertStringIncludes(success.output, 'héllo 🐈 "quotes" \\ backslash'); assertStringIncludes(success.output, root.path); const deno = Deno.execPath().replaceAll("'", "''"); assertEquals((await run(`& '${deno}' eval 'Deno.exit(23)'`)).exitCode, 23); assertEquals((await run("Write-Error 'failure'")).exitCode, 1); const long = await run("1..2500 | ForEach-Object { '🐈' + $_ }"); assert(long.spillPath); assertStringIncludes(await Deno.readTextFile(long.spillPath), "🐈2500"); const timedOut = await env.exec(await powershellCommand(env, "Start-Sleep -Seconds 5"), { timeout: 0.2, }, BACKGROUND_CONTEXT); assert(!timedOut.ok); assertEquals(timedOut.error.code, "timeout"); }, });