• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

skmtc / skmtc / 18763003556

23 Oct 2025 09:51PM UTC coverage: 42.296% (-0.2%) from 42.471%
18763003556

push

github

dmitrigrabov
Increment port number if requested is not available

510 of 604 branches covered (84.44%)

Branch coverage included in aggregate %.

5 of 109 new or added lines in 2 files covered. (4.59%)

148 existing lines in 5 files now uncovered.

4898 of 12182 relevant lines covered (40.21%)

13.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

4.31
/deno/cli/components/ServerTask.tsx
1
import React, { useId, useEffect } from 'react'
3✔
2
import { useState } from 'react'
3
import type { Project } from '@/lib/project.ts'
4
import { useTask } from './TaskContext.tsx'
3✔
5
import { Box } from 'ink'
3✔
6
import { dirname } from '@std/path/dirname'
3✔
7
import { join } from '@std/path/join'
3✔
8
import type { Dispatch, SetStateAction } from 'react'
9

NEW
10
const isPortAvailable = async (port: number): Promise<boolean> => {
×
NEW
11
  try {
×
NEW
12
    const conn = await Deno.connect({ port, hostname: 'localhost' })
×
NEW
13
    conn.close()
×
NEW
14
    return false // Port is in use
×
NEW
15
  } catch {
×
NEW
16
    return true // Port is available
×
NEW
17
  }
×
NEW
18
}
×
19

NEW
20
const findAvailablePort = async (startPort: number): Promise<number> => {
×
NEW
21
  let port = startPort
×
NEW
22
  while (!(await isPortAvailable(port))) {
×
NEW
23
    port++
×
NEW
24
  }
×
NEW
25
  return port
×
NEW
26
}
×
27

NEW
28
const waitForServerReady = async (port: number): Promise<void> => {
×
NEW
29
  const maxRetries = 60 // 60 attempts
×
NEW
30
  const initialBackoff = 100 // Start with 100ms
×
NEW
31
  const maxBackoff = 2000 // Cap at 2 seconds
×
NEW
32
  const timeout = 30000 // 30 second total timeout
×
33

NEW
34
  const startTime = Date.now()
×
35

NEW
36
  for (let attempt = 0; attempt < maxRetries; attempt++) {
×
37
    // Check if we've exceeded total timeout
NEW
38
    if (Date.now() - startTime > timeout) {
×
NEW
39
      throw new Error('Server startup timeout: /generators endpoint did not respond')
×
NEW
40
    }
×
41

NEW
42
    try {
×
NEW
43
      const response = await fetch(`http://localhost:${port}/generators`)
×
NEW
44
      if (response.ok) {
×
NEW
45
        return // Server is ready!
×
NEW
46
      }
×
NEW
47
    } catch {
×
48
      // Server not ready yet, continue retrying
NEW
49
    }
×
50

51
    // Exponential backoff with cap
NEW
52
    const backoffMs = Math.min(initialBackoff * Math.pow(2, attempt), maxBackoff)
×
NEW
53
    await new Promise((resolve) => setTimeout(resolve, backoffMs))
×
NEW
54
  }
×
55

NEW
56
  throw new Error('Server startup timeout: maximum retries exceeded')
×
NEW
57
}
×
58

59
type ServerTaskProps = {
60
  project: Project
61
  setChild: Dispatch<SetStateAction<Deno.ChildProcess | undefined>>
62
}
63

64
export const ServerTask = ({ project, setChild }: ServerTaskProps) => {
×
65
  const { dispatch } = useTask()
×
66

67
  useEffect(() => {
×
68
    const serve = async () => {
×
69
      const modPath = await project.createServer()
×
70

NEW
71
      const port = String(await findAvailablePort(8001))
×
72

73
      const serverUrl = `http://localhost:${port}`
×
74

75
      project.clientJson.contents = project.clientJson.contents
×
76
        ? {
×
77
            ...project.clientJson.contents,
×
78
            serverUrl
×
79
          }
×
80
        : {
×
81
            serverUrl,
×
82
            settings: {}
×
83
          }
×
84

85
      await project.clientJson.write()
×
86

NEW
87
      const child = await runServer({ modPath, port })
×
88

89
      setChild(child)
×
90
      dispatch({ type: 'increment-current-task' })
×
91
    }
×
92

93
    serve()
×
94
  }, [])
×
95

96
  return <Box></Box>
×
97

98
  // if (serving) {
99
  //   return (
100
  //     <TaskBox id={`${id}-result`} active={false}>
101
  //       <Spinner label="Serving..." />
102
  //     </TaskBox>
103
  //   )
104
  // }
105

106
  // return (
107
  //   <TaskContainer>
108
  //     <Spinner label="Starting server..." />
109
  //   </TaskContainer>
110
  // )
111
}
×
112

113
type RunServerArgs = {
114
  modPath: string
115
  port: string
116
}
117

NEW
118
export const runServer = async ({
×
NEW
119
  modPath,
×
NEW
120
  port = '8001'
×
NEW
121
}: RunServerArgs): Promise<Deno.ChildProcess> => {
×
122
  const command = new Deno.Command('deno', {
×
123
    args: ['serve', '--allow-env', '--allow-sys', '--port', port, modPath],
×
124
    cwd: dirname(modPath),
×
125
    stdout: 'piped',
×
126
    stderr: 'piped'
×
127
  })
×
128

129
  const logsPath = join(dirname(modPath), '.settings', 'logs.txt')
×
130
  const errorLogsPath = join(dirname(modPath), '.settings', 'error-logs.txt')
×
131

132
  // create subprocess and collect output
133
  const child = command.spawn()
×
134

135
  // Wait for server to be ready by polling /generators endpoint
NEW
136
  await waitForServerReady(Number(port))
×
137

138
  // Read stdout in the background and write to file
139
  ;(async () => {
×
140
    const reader = child.stdout.getReader()
×
141
    const decoder = new TextDecoder()
×
142
    const file = await Deno.open(logsPath, { create: true, append: true })
×
143
    try {
×
144
      while (true) {
×
145
        const { done, value } = await reader.read()
×
146
        if (done) break
×
147
        const text = decoder.decode(value, { stream: true })
×
148
        await file.write(new TextEncoder().encode(text))
×
149
      }
×
150
    } finally {
×
151
      reader.releaseLock()
×
152
      file.close()
×
153
    }
×
154
  })()
×
155

156
  // Read stderr in the background and write to file
157
  ;(async () => {
×
158
    const reader = child.stderr.getReader()
×
159
    const decoder = new TextDecoder()
×
160
    const file = await Deno.open(errorLogsPath, { create: true, append: true })
×
161
    try {
×
162
      while (true) {
×
163
        const { done, value } = await reader.read()
×
164
        if (done) break
×
165
        const text = decoder.decode(value, { stream: true })
×
166
        await file.write(new TextEncoder().encode(text))
×
167
      }
×
168
    } finally {
×
169
      reader.releaseLock()
×
170
      file.close()
×
171
    }
×
172
  })()
×
173

174
  return child
×
175
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc