|
1 | | -import { spawn, type ChildProcess } from 'node:child_process' |
2 | | -import * as net from 'node:net' |
3 | | -import * as path from 'node:path' |
4 | | -import * as fs from 'node:fs' |
| 1 | +import { startPrismaDevServer } from '@prisma/dev' |
5 | 2 |
|
6 | 3 | export interface PrismaDevServer { |
7 | 4 | url: string |
8 | 5 | stop: () => Promise<void> |
9 | 6 | } |
10 | 7 |
|
11 | | -async function waitForPort( |
12 | | - host: string, |
13 | | - port: number, |
14 | | - timeoutMs = 60000, |
15 | | -): Promise<void> { |
16 | | - const start = Date.now() |
17 | | - while (Date.now() - start < timeoutMs) { |
18 | | - try { |
19 | | - await new Promise<void>((resolve, reject) => { |
20 | | - const socket = net.createConnection({ host, port }) |
21 | | - socket.once('connect', () => { |
22 | | - socket.destroy() |
23 | | - resolve() |
24 | | - }) |
25 | | - socket.once('error', reject) |
26 | | - }) |
27 | | - return |
28 | | - } catch { |
29 | | - await new Promise((r) => setTimeout(r, 100)) |
30 | | - } |
31 | | - } |
32 | | - throw new Error(`Timeout waiting for ${host}:${port}`) |
33 | | -} |
34 | | - |
35 | 8 | export async function startPrismaDev(): Promise<PrismaDevServer> { |
36 | | - console.log('Starting Prisma Dev server as child process...') |
37 | | - |
38 | | - // Create a simple script to start the server |
39 | | - const scriptContent = ` |
40 | | -import { context, trace } from '@opentelemetry/api'; |
41 | | -import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks'; |
42 | | -import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; |
43 | | -import { startPrismaDevServer } from '@prisma/dev'; |
44 | | -
|
45 | | -context.setGlobalContextManager(new AsyncLocalStorageContextManager()); |
46 | | -trace.setGlobalTracerProvider(new BasicTracerProvider()); |
47 | | -
|
48 | | -const server = await startPrismaDevServer({ databaseIdleTimeoutMillis: 300000 }); |
49 | | -console.log(server.database.connectionString); |
50 | | -
|
51 | | -process.once('SIGTERM', async () => { |
52 | | - await server.close(); |
53 | | - process.exit(0); |
54 | | -}); |
55 | | -
|
56 | | -process.once('SIGINT', async () => { |
57 | | - await server.close(); |
58 | | - process.exit(0); |
59 | | -}); |
60 | | -` |
61 | | - |
62 | | - const uniqueId = Math.random().toString(36).substring(2, 15) |
63 | | - const tempScriptPath = path.join( |
64 | | - process.cwd(), |
65 | | - `.prisma-dev-server-${uniqueId}.mjs`, |
66 | | - ) |
67 | | - fs.writeFileSync(tempScriptPath, scriptContent) |
68 | | - |
69 | | - return new Promise((resolve, reject) => { |
70 | | - let url = '' |
71 | | - let childProcess: ChildProcess |
72 | | - |
73 | | - childProcess = spawn('node', [tempScriptPath], { |
74 | | - stdio: ['ignore', 'pipe', 'pipe'], |
75 | | - detached: false, |
76 | | - }) |
77 | | - |
78 | | - const timeout = setTimeout(() => { |
79 | | - childProcess.kill() |
80 | | - fs.unlinkSync(tempScriptPath) |
81 | | - reject(new Error('Timeout waiting for Prisma Dev server URL')) |
82 | | - }, 60000) |
83 | | - |
84 | | - childProcess.stdout?.on('data', async (data: Buffer) => { |
85 | | - const output = data.toString().trim() |
86 | | - console.log(`[prisma-dev] ${output}`) |
87 | | - |
88 | | - if (output.startsWith('postgres://')) { |
89 | | - url = output |
90 | | - clearTimeout(timeout) |
91 | | - |
92 | | - const urlObj = new URL(url) |
93 | | - const host = urlObj.hostname |
94 | | - const port = parseInt(urlObj.port, 10) |
95 | | - |
96 | | - // Replace localhost with 127.0.0.1 for consistency |
97 | | - const fixedUrl = url.replace('localhost', '127.0.0.1') |
98 | | - url = fixedUrl |
99 | | - |
100 | | - console.log(`Prisma Dev server started: ${url}`) |
101 | | - console.log(`Waiting for database at ${host}:${port}...`) |
102 | | - |
103 | | - try { |
104 | | - await waitForPort(host, port) |
105 | | - console.log('Database is ready!') |
106 | | - |
107 | | - resolve({ |
108 | | - url, |
109 | | - stop: async () => { |
110 | | - console.log('Stopping Prisma Dev server...') |
111 | | - childProcess.kill('SIGTERM') |
112 | | - // Wait for process to exit |
113 | | - await new Promise<void>((res) => { |
114 | | - childProcess.on('exit', () => res()) |
115 | | - setTimeout(res, 5000) // Timeout after 5s |
116 | | - }) |
117 | | - try { |
118 | | - fs.unlinkSync(tempScriptPath) |
119 | | - } catch {} |
120 | | - console.log('Prisma Dev server stopped.') |
121 | | - }, |
122 | | - }) |
123 | | - } catch (err) { |
124 | | - childProcess.kill() |
125 | | - fs.unlinkSync(tempScriptPath) |
126 | | - reject(err) |
127 | | - } |
128 | | - } |
129 | | - }) |
130 | | - |
131 | | - childProcess.stderr?.on('data', (data: Buffer) => { |
132 | | - console.error(`[prisma-dev stderr] ${data.toString().trim()}`) |
133 | | - }) |
134 | | - |
135 | | - childProcess.on('error', (err) => { |
136 | | - clearTimeout(timeout) |
137 | | - try { |
138 | | - fs.unlinkSync(tempScriptPath) |
139 | | - } catch {} |
140 | | - reject(err) |
141 | | - }) |
142 | | - |
143 | | - childProcess.on('exit', (code) => { |
144 | | - if (!url) { |
145 | | - clearTimeout(timeout) |
146 | | - try { |
147 | | - fs.unlinkSync(tempScriptPath) |
148 | | - } catch {} |
149 | | - reject(new Error(`Prisma Dev server exited with code ${code}`)) |
150 | | - } |
151 | | - }) |
| 9 | + const server = await startPrismaDevServer({ |
| 10 | + databaseIdleTimeoutMillis: 300000, |
152 | 11 | }) |
| 12 | + const url = server.database.connectionString.replace('localhost', '127.0.0.1') |
| 13 | + |
| 14 | + return { |
| 15 | + url, |
| 16 | + stop: async () => { |
| 17 | + await server.close() |
| 18 | + }, |
| 19 | + } |
153 | 20 | } |
0 commit comments