@@ -4,9 +4,44 @@ import path from "node:path";
44
55const command = process . argv [ 2 ] ;
66const scratchRoot = process . env . OPENCLAW_PLUGINS_TMP_DIR || os . tmpdir ( ) ;
7+ const CLAWHUB_PREFLIGHT_TIMEOUT_MS = readPositiveInt (
8+ process . env . OPENCLAW_PLUGINS_E2E_CLAWHUB_PREFLIGHT_TIMEOUT_MS ,
9+ 30_000 ,
10+ ) ;
711const readJson = ( file ) => JSON . parse ( fs . readFileSync ( file , "utf8" ) ) ;
812const scratchFile = ( name ) => path . join ( scratchRoot , name ) ;
913
14+ function readPositiveInt ( raw , fallback ) {
15+ const parsed = Number . parseInt ( String ( raw || "" ) , 10 ) ;
16+ return Number . isInteger ( parsed ) && parsed > 0 ? parsed : fallback ;
17+ }
18+
19+ function createTimeoutError ( label , timeoutMs ) {
20+ const error = new Error ( `${ label } timed out after ${ timeoutMs } ms` ) ;
21+ error . code = "ETIMEDOUT" ;
22+ return error ;
23+ }
24+
25+ async function withTimeout ( label , timeoutMs , run ) {
26+ const controller = new AbortController ( ) ;
27+ const timeoutError = createTimeoutError ( label , timeoutMs ) ;
28+ let timeout ;
29+ const timeoutPromise = new Promise ( ( _ , reject ) => {
30+ timeout = setTimeout ( ( ) => {
31+ controller . abort ( timeoutError ) ;
32+ reject ( timeoutError ) ;
33+ } , timeoutMs ) ;
34+ timeout . unref ?. ( ) ;
35+ } ) ;
36+ try {
37+ return await Promise . race ( [ run ( controller . signal ) , timeoutPromise ] ) ;
38+ } finally {
39+ if ( timeout ) {
40+ clearTimeout ( timeout ) ;
41+ }
42+ }
43+ }
44+
1045function resolveHomePath ( value ) {
1146 if ( value === "~" ) {
1247 return process . env . HOME ;
@@ -420,12 +455,18 @@ function assertGitPlugin() {
420455 }
421456 assertRealPathInside ( installPath , dependencyPackagePath , "git plugin installed dependency" ) ;
422457 fs . writeFileSync ( scratchFile ( "plugins-git-install-path.txt" ) , installPath , "utf8" ) ;
423- fs . writeFileSync ( scratchFile ( "plugins-git-install-parent.txt" ) , path . dirname ( installPath ) , "utf8" ) ;
458+ fs . writeFileSync (
459+ scratchFile ( "plugins-git-install-parent.txt" ) ,
460+ path . dirname ( installPath ) ,
461+ "utf8" ,
462+ ) ;
424463}
425464
426465function assertGitPluginRemoved ( ) {
427466 const installPath = fs . readFileSync ( scratchFile ( "plugins-git-install-path.txt" ) , "utf8" ) . trim ( ) ;
428- const installParent = fs . readFileSync ( scratchFile ( "plugins-git-install-parent.txt" ) , "utf8" ) . trim ( ) ;
467+ const installParent = fs
468+ . readFileSync ( scratchFile ( "plugins-git-install-parent.txt" ) , "utf8" )
469+ . trim ( ) ;
429470 assertPluginRemoved ( {
430471 pluginId : "demo-plugin-git" ,
431472 listFile : scratchFile ( "plugins-git-uninstalled.json" ) ,
@@ -597,7 +638,10 @@ function assertNpmPlugin() {
597638}
598639
599640function assertNpmPluginUpdateUnchanged ( ) {
600- assertUpdateOutput ( scratchFile ( "plugins-npm-update.log" ) , "demo-plugin-npm is up to date (0.0.1)." ) ;
641+ assertUpdateOutput (
642+ scratchFile ( "plugins-npm-update.log" ) ,
643+ "demo-plugin-npm is up to date (0.0.1)." ,
644+ ) ;
601645 assertNpmPlugin ( ) ;
602646}
603647
@@ -748,16 +792,31 @@ async function assertClawHubPreflight() {
748792 process . env . CLAWHUB_TOKEN ||
749793 process . env . CLAWHUB_AUTH_TOKEN ||
750794 "" ;
751- const response = await fetch ( `${ baseUrl } /api/v1/packages/${ encodeURIComponent ( packageName ) } ` , {
752- headers : token ? { Authorization : `Bearer ${ token } ` } : undefined ,
753- } ) ;
795+ const preflightUrl = `${ baseUrl } /api/v1/packages/${ encodeURIComponent ( packageName ) } ` ;
796+ const response = await withTimeout (
797+ `ClawHub package preflight for ${ packageName } ` ,
798+ CLAWHUB_PREFLIGHT_TIMEOUT_MS ,
799+ ( signal ) =>
800+ fetch ( preflightUrl , {
801+ headers : token ? { Authorization : `Bearer ${ token } ` } : undefined ,
802+ signal,
803+ } ) ,
804+ ) ;
754805 if ( ! response . ok ) {
755- const body = await response . text ( ) . catch ( ( ) => "" ) ;
806+ const body = await withTimeout (
807+ `ClawHub package preflight response for ${ packageName } ` ,
808+ CLAWHUB_PREFLIGHT_TIMEOUT_MS ,
809+ ( ) => response . text ( ) . catch ( ( ) => "" ) ,
810+ ) ;
756811 throw new Error (
757812 `ClawHub package preflight failed for ${ packageName } : ${ response . status } ${ body } ` ,
758813 ) ;
759814 }
760- const detail = await response . json ( ) ;
815+ const detail = await withTimeout (
816+ `ClawHub package preflight response for ${ packageName } ` ,
817+ CLAWHUB_PREFLIGHT_TIMEOUT_MS ,
818+ ( ) => response . json ( ) ,
819+ ) ;
761820 const family = detail . package ?. family ;
762821 if ( family !== "code-plugin" && family !== "bundle-plugin" ) {
763822 throw new Error ( `ClawHub package ${ packageName } is not installable as a plugin: ${ family } ` ) ;
@@ -834,7 +893,9 @@ function assertClawHubInstalled() {
834893
835894function assertClawHubRemoved ( ) {
836895 const pluginId = process . env . CLAWHUB_PLUGIN_ID ;
837- const installPath = fs . readFileSync ( scratchFile ( "plugins-clawhub-install-path.txt" ) , "utf8" ) . trim ( ) ;
896+ const installPath = fs
897+ . readFileSync ( scratchFile ( "plugins-clawhub-install-path.txt" ) , "utf8" )
898+ . trim ( ) ;
838899 const list = readJson ( scratchFile ( "plugins-clawhub-uninstalled.json" ) ) ;
839900 if ( ( list . plugins || [ ] ) . some ( ( entry ) => entry . id === pluginId ) ) {
840901 throw new Error ( `ClawHub plugin still listed after uninstall: ${ pluginId } ` ) ;
0 commit comments