@@ -6,7 +6,12 @@ import { createScriptTestHarness } from "./test-helpers";
66
77const SCRIPT_PATH = "scripts/install.ps1" ;
88const ENTRYPOINT_RE =
9- / \r ? \n \$ m a i n R e s u l t s = @ \( M a i n \) \r ? \n \$ i n s t a l l S u c c e e d e d = \$ m a i n R e s u l t s \. C o u n t - g t 0 - a n d \$ m a i n R e s u l t s \[ - 1 \] - e q \$ t r u e \r ? \n C o m p l e t e - I n s t a l l - S u c c e e d e d : \$ i n s t a l l S u c c e e d e d \s * $ / m;
9+ / \r ? \n \$ m a i n R e s u l t s = @ \( M a i n \) \r ? \n \$ i n s t a l l S u c c e e d e d = T e s t - B o o l e a n S u c c e s s R e s u l t - R e s u l t s \$ m a i n R e s u l t s \r ? \n C o m p l e t e - I n s t a l l - S u c c e e d e d : \$ i n s t a l l S u c c e e d e d \s * $ / m;
10+ const ENTRYPOINT_LINES = [
11+ "$mainResults = @(Main)" ,
12+ "$installSucceeded = Test-BooleanSuccessResult -Results $mainResults" ,
13+ "Complete-Install -Succeeded:$installSucceeded" ,
14+ ] ;
1015
1116function extractFunctionBody ( source : string , name : string ) : string {
1217 const match = source . match (
@@ -50,9 +55,7 @@ function createFailingNodeFixture(source: string): string {
5055 "function Check-Node { return $false }" ,
5156 "function Install-Node { return $false }" ,
5257 "" ,
53- "$mainResults = @(Main)" ,
54- "$installSucceeded = $mainResults.Count -gt 0 -and $mainResults[-1] -eq $true" ,
55- "Complete-Install -Succeeded:$installSucceeded" ,
58+ ...ENTRYPOINT_LINES ,
5659 "" ,
5760 ] . join ( "\n" ) ;
5861}
@@ -77,9 +80,12 @@ describe("install.ps1 failure handling", () => {
7780
7881 it ( "keeps failure termination in the top-level completion handler" , ( ) => {
7982 const completeInstallBody = extractFunctionBody ( source , "Complete-Install" ) ;
83+ const booleanSuccessBody = extractFunctionBody ( source , "Test-BooleanSuccessResult" ) ;
8084 expect ( completeInstallBody ) . toMatch ( / \$ P S C o m m a n d P a t h / ) ;
8185 expect ( completeInstallBody ) . toMatch ( / \b e x i t \$ s c r i p t : I n s t a l l E x i t C o d e \b / ) ;
8286 expect ( completeInstallBody ) . toMatch ( / \b t h r o w " O p e n C l a w i n s t a l l a t i o n f a i l e d w i t h e x i t c o d e / ) ;
87+ expect ( booleanSuccessBody ) . toContain ( "$Results.Count -gt 0" ) ;
88+ expect ( source ) . toContain ( "$installSucceeded = Test-BooleanSuccessResult -Results $mainResults" ) ;
8389 } ) ;
8490
8591 it ( "runs npm install through the resolved command with quiet CI defaults" , ( ) => {
@@ -227,6 +233,7 @@ describe("install.ps1 failure handling", () => {
227233 const pnpmVersionMatchBody = extractFunctionBody ( source , "Test-PnpmCommandMatchesVersion" ) ;
228234 const ensurePnpmBody = extractFunctionBody ( source , "Ensure-Pnpm" ) ;
229235 const gitInstallBody = extractFunctionBody ( source , "Install-OpenClawFromGit" ) ;
236+ const mainBody = extractFunctionBody ( source , "Main" ) ;
230237
231238 expect ( pnpmVersionBody ) . toContain ( "package.json" ) ;
232239 expect ( pnpmVersionBody ) . toContain ( "$packageJson.packageManager -match '^pnpm@(?<version>[^+]+)'" ) ;
@@ -247,9 +254,31 @@ describe("install.ps1 failure handling", () => {
247254 expect ( gitInstallBody . indexOf ( "git -C $RepoDir pull --rebase" ) ) . toBeLessThan (
248255 gitInstallBody . indexOf ( "Ensure-Pnpm -RepoDir $RepoDir" ) ,
249256 ) ;
257+ expect ( mainBody ) . toContain ( "$gitInstallResults = @(Install-OpenClawFromGit" ) ;
258+ expect ( mainBody ) . toContain (
259+ "Test-BooleanSuccessResult -Results $gitInstallResults" ,
260+ ) ;
261+ expect ( mainBody ) . toContain ( "$npmInstallResults = @(Install-OpenClaw)" ) ;
262+ expect ( mainBody ) . toContain (
263+ "Test-BooleanSuccessResult -Results $npmInstallResults" ,
264+ ) ;
250265 expect ( gitInstallBody ) . toContain ( "Push-Location -LiteralPath $RepoDir" ) ;
251266 expect ( gitInstallBody ) . toContain ( "& $pnpmCommand install" ) ;
267+ expect ( gitInstallBody ) . toContain (
268+ 'Write-Host "[!] pnpm install failed for the Git checkout"' ,
269+ ) ;
270+ expect ( gitInstallBody ) . toContain ( "& $pnpmCommand build" ) ;
271+ expect ( gitInstallBody ) . toContain (
272+ 'Write-Host "[!] pnpm build failed for the Git checkout"' ,
273+ ) ;
274+ expect ( gitInstallBody ) . toContain ( '$entryPath = Join-Path $RepoDir "dist\\\\entry.js"' ) ;
275+ expect ( gitInstallBody ) . toContain ( "Test-Path $entryPath" ) ;
276+ expect ( gitInstallBody ) . toContain (
277+ 'Write-Host "[!] OpenClaw build did not produce $entryPath"' ,
278+ ) ;
279+ expect ( gitInstallBody ) . toContain ( 'node ""$entryPath"" %*' ) ;
252280 expect ( gitInstallBody ) . not . toContain ( "& $pnpmCommand -C $RepoDir install" ) ;
281+ expect ( gitInstallBody ) . not . toContain ( 'node ""$RepoDir\\\\dist\\\\entry.js"" %*' ) ;
253282 } ) ;
254283
255284 it ( "cleans legacy git submodules only from the selected git checkout" , ( ) => {
@@ -309,6 +338,42 @@ describe("install.ps1 failure handling", () => {
309338 expect ( result . stdout ) . toContain ( "alive-after-install" ) ;
310339 } ) ;
311340
341+ runIfPowerShell ( "treats noisy Git install false as failure" , ( ) => {
342+ const tempDir = harness . createTempDir ( "openclaw-install-ps1-" ) ;
343+ const scriptPath = join ( tempDir , "install.ps1" ) ;
344+ const scriptWithoutEntryPoint = source . replace ( ENTRYPOINT_RE , "" ) ;
345+ writeFileSync (
346+ scriptPath ,
347+ [
348+ scriptWithoutEntryPoint ,
349+ "" ,
350+ "function Write-Banner { }" ,
351+ "function Ensure-ExecutionPolicy { return $true }" ,
352+ "function Check-Node { return $true }" ,
353+ "function Check-ExistingOpenClaw { return $false }" ,
354+ "function Get-NpmCommandPath { return $null }" ,
355+ "function Install-OpenClawFromGit {" ,
356+ " Write-Output 'pnpm stdout before failure'" ,
357+ " return $false" ,
358+ "}" ,
359+ "function Ensure-OpenClawOnPath { throw 'should not continue after failed git install' }" ,
360+ "$InstallMethod = 'git'" ,
361+ "$GitDir = 'C:\\\\openclaw-test'" ,
362+ "$NoOnboard = $true" ,
363+ "$result = Main" ,
364+ 'if ($result -ne $false) { throw "Main returned $result" }' ,
365+ 'if ($script:InstallExitCode -ne 1) { throw "InstallExitCode=$script:InstallExitCode" }' ,
366+ "" ,
367+ ] . join ( "\n" ) ,
368+ ) ;
369+ chmodSync ( scriptPath , 0o755 ) ;
370+
371+ const result = runPowerShell ( [ "-NoLogo" , "-NoProfile" , "-Command" , `. ${ toPowerShellSingleQuotedLiteral ( scriptPath ) } ` ] ) ;
372+
373+ expect ( result . status ) . toBe ( 0 ) ;
374+ expect ( result . stderr ) . toBe ( "" ) ;
375+ } ) ;
376+
312377 runIfPowerShell ( "keeps npm chatter out of Main's success return value" , ( ) => {
313378 const tempDir = harness . createTempDir ( "openclaw-install-ps1-" ) ;
314379 const scriptPath = join ( tempDir , "install.ps1" ) ;
@@ -371,9 +436,7 @@ describe("install.ps1 failure handling", () => {
371436 "function Refresh-GatewayServiceIfLoaded { }" ,
372437 "function Invoke-OpenClawCommand { return 'OpenClaw test-version' }" ,
373438 "$NoOnboard = $true" ,
374- "$mainResults = @(Main)" ,
375- "$installSucceeded = $mainResults.Count -gt 0 -and $mainResults[-1] -eq $true" ,
376- "Complete-Install -Succeeded:$installSucceeded" ,
439+ ...ENTRYPOINT_LINES ,
377440 "" ,
378441 ] . join ( "\n" ) ,
379442 ) ;
0 commit comments