@@ -134,6 +134,59 @@ dg_ensure_node_gyp() {
134134 export PATH=" $node_gyp_bin :$PATH "
135135}
136136
137+ dg_pick_free_port () {
138+ local first_port=" $1 "
139+ local last_port=" $2 "
140+ local host=" ${3:- 127.0.0.1} "
141+
142+ if ! command -v node > /dev/null 2>&1 ; then
143+ echo " e2e: node is required to pick a free TCP port" >&2
144+ return 1
145+ fi
146+
147+ node - " $first_port " " $last_port " " $host " << 'NODE '
148+ const net = require("node:net")
149+
150+ const [firstRaw, lastRaw, host] = process.argv.slice(2)
151+ const first = Number.parseInt(firstRaw, 10)
152+ const last = Number.parseInt(lastRaw, 10)
153+
154+ if (!Number.isInteger(first) || !Number.isInteger(last) || first < 1 || last > 65535 || first > last) {
155+ console.error(`e2e: invalid port range: ${firstRaw}-${lastRaw}`)
156+ process.exit(1)
157+ }
158+
159+ const canListen = (port) =>
160+ new Promise((resolve) => {
161+ const server = net.createServer()
162+ server.unref()
163+ server.once("error", () => resolve(false))
164+ server.listen({ host, port, exclusive: true }, () => {
165+ server.close(() => resolve(true))
166+ })
167+ })
168+
169+ ;(async () => {
170+ const count = last - first + 1
171+ const start = Math.floor(Math.random() * count)
172+
173+ for (let offset = 0; offset < count; offset += 1) {
174+ const port = first + ((start + offset) % count)
175+ if (await canListen(port)) {
176+ console.log(port)
177+ return
178+ }
179+ }
180+
181+ console.error(`e2e: no free TCP port on ${host} in range ${first}-${last}`)
182+ process.exit(1)
183+ })().catch((error) => {
184+ console.error(error instanceof Error ? error.message : String(error))
185+ process.exit(1)
186+ })
187+ NODE
188+ }
189+
137190dg_controller_container_name () {
138191 printf ' %s\n' " ${DOCKER_GIT_API_CONTAINER_NAME:- docker-git-api} "
139192}
0 commit comments