11import { Code } from 'mdast' ;
2+ import { createConnection } from 'net' ;
23import { join } from 'path' ;
34import { Option , assert } from 'ts-std' ;
45import { parseCommand } from '../../commands' ;
56import Options from '../../options' ;
67import parseArgs , { ToBool , optional } from '../../parse-args' ;
78import Servers from '../../servers' ;
89
10+ const DEFAULT_READY_TIMEOUT = 30000 ;
11+ const READY_RETRY_INTERVAL = 500 ;
12+
913interface Args {
1014 id ?: string ;
1115 lang ?: string ;
@@ -17,6 +21,64 @@ interface Args {
1721 captureOutput ?: boolean ;
1822}
1923
24+ function extractURL ( expect : Option < string > ) : Option < string > {
25+ if ( ! expect ) {
26+ return null ;
27+ }
28+
29+ let match = expect . match ( / h t t p s ? : \/ \/ [ ^ \s " ' ] + / ) ;
30+
31+ if ( match ) {
32+ return match [ 0 ] ! ;
33+ } else {
34+ return null ;
35+ }
36+ }
37+
38+ async function waitForServerReady ( url : string , timeout : number ) : Promise < void > {
39+ let parsed = new URL ( url ) ;
40+ let host = parsed . hostname ;
41+ let port = parsed . port ? Number ( parsed . port ) : ( parsed . protocol === 'https:' ? 443 : 80 ) ;
42+ let startedAt = Date . now ( ) ;
43+ let lastError : Option < Error > = null ;
44+
45+ while ( Date . now ( ) - startedAt < timeout ) {
46+ try {
47+ await new Promise < void > ( ( resolve , reject ) => {
48+ let socket = createConnection ( { host, port } ) ;
49+
50+ socket . once ( 'connect' , ( ) => {
51+ socket . end ( ) ;
52+ resolve ( ) ;
53+ } ) ;
54+
55+ socket . once ( 'error' , e => {
56+ socket . destroy ( ) ;
57+
58+ if ( e instanceof Error ) {
59+ reject ( e ) ;
60+ } else {
61+ reject ( new Error ( String ( e ) ) ) ;
62+ }
63+ } ) ;
64+ } ) ;
65+
66+ return ;
67+ } catch ( e ) {
68+ if ( e instanceof Error ) {
69+ lastError = e ;
70+ } else {
71+ lastError = new Error ( String ( e ) ) ;
72+ }
73+ }
74+
75+ await new Promise ( resolve => setTimeout ( resolve , READY_RETRY_INTERVAL ) ) ;
76+ }
77+
78+ let details = lastError ? ` Last error: ${ lastError . message } ` : '' ;
79+ throw new Error ( `Timed out while waiting for ${ url } to accept connections after ${ timeout } ms.${ details } ` ) ;
80+ }
81+
2082export default async function startServer ( node : Code , options : Options , servers : Servers ) : Promise < Option < Code > > {
2183 let args = parseArgs < Args > ( node , [
2284 optional ( 'id' , String ) ,
@@ -72,7 +134,36 @@ export default async function startServer(node: Code, options: Options, servers:
72134 output . push ( `$ ${ display } ` ) ;
73135 }
74136
75- let stdout = await server . start ( args . expect ) ;
137+ let stdout = await server . start ( args . expect , args . timeout ) ;
138+
139+ let readyURL = extractURL ( args . expect ) ;
140+
141+ if ( readyURL ) {
142+ let timeout = args . timeout || DEFAULT_READY_TIMEOUT ;
143+
144+ try {
145+ await waitForServerReady ( readyURL , timeout ) ;
146+ } catch ( e ) {
147+ await server . kill ( ) ;
148+
149+ let message = ( e instanceof Error ) ? e . message : String ( e ) ;
150+
151+ throw new Error (
152+ `${ message }
153+
154+ ====== STDOUT ======
155+
156+ ${ server . stdout || '(No output)' }
157+
158+ ====== STDERR ======
159+
160+ ${ server . stderr || '(No output)' }
161+
162+ ====================
163+ `
164+ ) ;
165+ }
166+ }
76167
77168 if ( args . captureOutput && stdout ) {
78169 output . push ( stdout ) ;
0 commit comments