@webstir-io/webstir-backend 0.1.15 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -79
- package/dist/add.d.ts +59 -0
- package/dist/add.js +626 -0
- package/dist/build/artifacts.d.ts +115 -1
- package/dist/build/artifacts.js +4 -4
- package/dist/build/entries.js +1 -1
- package/dist/build/pipeline.d.ts +33 -1
- package/dist/build/pipeline.js +307 -65
- package/dist/cache/diff.js +9 -8
- package/dist/cache/reporters.js +1 -1
- package/dist/deploy-cli.d.ts +2 -0
- package/dist/deploy-cli.js +86 -0
- package/dist/diagnostics/summary.js +2 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -0
- package/dist/manifest/pipeline.js +103 -32
- package/dist/provider.js +35 -17
- package/dist/runtime/bun.d.ts +51 -0
- package/dist/runtime/bun.js +499 -0
- package/dist/runtime/core.d.ts +141 -0
- package/dist/runtime/core.js +316 -0
- package/dist/runtime/deploy-backend.d.ts +20 -0
- package/dist/runtime/deploy-backend.js +175 -0
- package/dist/runtime/deploy-shared.d.ts +43 -0
- package/dist/runtime/deploy-shared.js +75 -0
- package/dist/runtime/deploy-static.d.ts +2 -0
- package/dist/runtime/deploy-static.js +161 -0
- package/dist/runtime/deploy.d.ts +3 -0
- package/dist/runtime/deploy.js +91 -0
- package/dist/runtime/forms.d.ts +73 -0
- package/dist/runtime/forms.js +236 -0
- package/dist/runtime/request-hooks.d.ts +47 -0
- package/dist/runtime/request-hooks.js +102 -0
- package/dist/runtime/session-metadata.d.ts +13 -0
- package/dist/runtime/session-metadata.js +98 -0
- package/dist/runtime/session-runtime.d.ts +28 -0
- package/dist/runtime/session-runtime.js +180 -0
- package/dist/runtime/session.d.ts +83 -0
- package/dist/runtime/session.js +396 -0
- package/dist/runtime/views.d.ts +74 -0
- package/dist/runtime/views.js +221 -0
- package/dist/scaffold/assets.js +25 -21
- package/dist/testing/context.js +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +100 -56
- package/dist/utils/bun.d.ts +2 -0
- package/dist/utils/bun.js +13 -0
- package/dist/watch.d.ts +13 -1
- package/dist/watch.js +345 -97
- package/dist/workspace.d.ts +8 -0
- package/dist/workspace.js +44 -3
- package/package.json +49 -14
- package/scripts/publish.sh +2 -92
- package/scripts/smoke.mjs +282 -107
- package/scripts/update-contract.sh +12 -10
- package/src/add.ts +964 -0
- package/src/build/artifacts.ts +49 -46
- package/src/build/entries.ts +12 -12
- package/src/build/pipeline.ts +779 -403
- package/src/cache/diff.ts +111 -105
- package/src/cache/reporters.ts +26 -26
- package/src/deploy-cli.ts +111 -0
- package/src/diagnostics/summary.ts +28 -22
- package/src/index.ts +11 -0
- package/src/manifest/pipeline.ts +328 -215
- package/src/provider.ts +115 -98
- package/src/runtime/bun.ts +793 -0
- package/src/runtime/core.ts +598 -0
- package/src/runtime/deploy-backend.ts +239 -0
- package/src/runtime/deploy-shared.ts +136 -0
- package/src/runtime/deploy-static.ts +191 -0
- package/src/runtime/deploy.ts +143 -0
- package/src/runtime/forms.ts +364 -0
- package/src/runtime/request-hooks.ts +165 -0
- package/src/runtime/session-metadata.ts +135 -0
- package/src/runtime/session-runtime.ts +267 -0
- package/src/runtime/session.ts +642 -0
- package/src/runtime/views.ts +385 -0
- package/src/scaffold/assets.ts +77 -73
- package/src/testing/context.js +8 -9
- package/src/testing/context.ts +9 -9
- package/src/testing/index.d.ts +14 -3
- package/src/testing/index.js +254 -175
- package/src/testing/index.ts +298 -195
- package/src/testing/types.d.ts +18 -19
- package/src/testing/types.ts +18 -18
- package/src/utils/bun.ts +26 -0
- package/src/watch.ts +503 -99
- package/src/workspace.ts +59 -3
- package/templates/backend/.env.example +15 -0
- package/templates/backend/auth/adapter.ts +335 -36
- package/templates/backend/db/connection.ts +190 -65
- package/templates/backend/db/migrate.ts +149 -43
- package/templates/backend/db/types.d.ts +1 -1
- package/templates/backend/env.ts +132 -20
- package/templates/backend/functions/hello/index.ts +1 -2
- package/templates/backend/index.ts +15 -508
- package/templates/backend/jobs/nightly/index.ts +1 -1
- package/templates/backend/jobs/runtime.ts +24 -11
- package/templates/backend/jobs/scheduler.ts +208 -46
- package/templates/backend/module.ts +227 -13
- package/templates/backend/observability/logger.ts +2 -12
- package/templates/backend/observability/metrics.ts +8 -5
- package/templates/backend/session/sqlite.ts +152 -0
- package/templates/backend/session/store.ts +45 -0
- package/templates/backend/tsconfig.json +1 -1
- package/tests/add.test.js +327 -0
- package/tests/authAdapter.test.js +315 -0
- package/tests/bundlerParity.test.js +217 -0
- package/tests/cacheReporter.test.js +10 -10
- package/tests/dbConnection.test.js +209 -0
- package/tests/deploy.test.js +357 -0
- package/tests/envLoader.test.js +271 -17
- package/tests/integration.test.js +2432 -3
- package/tests/jobsScheduler.test.js +253 -0
- package/tests/manifest.test.js +287 -12
- package/tests/migrationRunner.test.js +249 -0
- package/tests/sessionScaffoldStore.test.js +752 -0
- package/tests/sessionStore.test.js +490 -0
- package/tests/testing.test.js +252 -0
- package/tests/watch.test.js +192 -32
- package/tsconfig.json +3 -10
- package/templates/backend/server/fastify.ts +0 -288
package/src/testing/index.js
CHANGED
|
@@ -2,207 +2,286 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { once } from 'node:events';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
4
|
import { readFile } from 'node:fs/promises';
|
|
5
|
-
import net from 'node:net';
|
|
6
5
|
import path from 'node:path';
|
|
6
|
+
|
|
7
7
|
import { getBackendTestContext, setBackendTestContext } from './context.js';
|
|
8
|
+
import { resolveWorkspaceRoot } from '../workspace.js';
|
|
9
|
+
|
|
8
10
|
const DEFAULT_PORT = 4100;
|
|
9
11
|
const DEFAULT_READY_TEXT = 'API server running';
|
|
10
12
|
const DEFAULT_READY_TIMEOUT_MS = 15_000;
|
|
13
|
+
|
|
11
14
|
export { getBackendTestContext, setBackendTestContext };
|
|
12
15
|
export async function createBackendTestHarness(options = {}) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
16
|
+
const resolvedEnv = { ...process.env, ...(options.env ?? {}) };
|
|
17
|
+
const workspaceRoot = resolveWorkspaceRoot({
|
|
18
|
+
workspaceRoot: options.workspaceRoot,
|
|
19
|
+
env: resolvedEnv,
|
|
20
|
+
});
|
|
21
|
+
const buildRoot = resolveWorkspacePath(
|
|
22
|
+
workspaceRoot,
|
|
23
|
+
options.buildRoot ??
|
|
24
|
+
resolvedEnv.WEBSTIR_BACKEND_BUILD_ROOT ??
|
|
25
|
+
path.join(workspaceRoot, 'build', 'backend'),
|
|
26
|
+
);
|
|
27
|
+
const entry = resolveWorkspacePath(
|
|
28
|
+
workspaceRoot,
|
|
29
|
+
options.entry ?? resolvedEnv.WEBSTIR_BACKEND_TEST_ENTRY ?? path.join(buildRoot, 'index.js'),
|
|
30
|
+
);
|
|
31
|
+
const manifestPath = resolveWorkspacePath(
|
|
32
|
+
workspaceRoot,
|
|
33
|
+
options.manifestPath ??
|
|
34
|
+
resolvedEnv.WEBSTIR_BACKEND_TEST_MANIFEST ??
|
|
35
|
+
path.join(workspaceRoot, '.webstir', 'backend-manifest.json'),
|
|
36
|
+
);
|
|
37
|
+
const readyText =
|
|
38
|
+
options.readyText ?? resolvedEnv.WEBSTIR_BACKEND_TEST_READY ?? DEFAULT_READY_TEXT;
|
|
39
|
+
const readyTimeoutMs =
|
|
40
|
+
options.readyTimeoutMs ??
|
|
41
|
+
readInt(resolvedEnv.WEBSTIR_BACKEND_TEST_READY_TIMEOUT, DEFAULT_READY_TIMEOUT_MS);
|
|
42
|
+
if (!existsSync(entry)) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Backend test entry not found at ${entry}. Run the backend build before executing backend tests.`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
const manifest = await loadManifest(manifestPath);
|
|
48
|
+
const requestedPort =
|
|
49
|
+
options.port ?? readInt(resolvedEnv.WEBSTIR_BACKEND_TEST_PORT, DEFAULT_PORT);
|
|
50
|
+
const { child, env, port } = await startBackendTestProcess({
|
|
51
|
+
workspaceRoot,
|
|
52
|
+
entry,
|
|
53
|
+
requestedPort,
|
|
54
|
+
baseEnv: resolvedEnv,
|
|
55
|
+
overrides: options.env,
|
|
56
|
+
readyText,
|
|
57
|
+
readyTimeoutMs,
|
|
58
|
+
});
|
|
59
|
+
const baseUrl = new URL(env.API_BASE_URL ?? `http://127.0.0.1:${port}`);
|
|
60
|
+
const context = {
|
|
61
|
+
baseUrl: baseUrl.toString(),
|
|
62
|
+
url: baseUrl,
|
|
63
|
+
port,
|
|
64
|
+
manifest,
|
|
65
|
+
routes: Array.isArray(manifest?.routes) ? manifest.routes : [],
|
|
66
|
+
env,
|
|
67
|
+
request: async (pathOrUrl = '/', init) => {
|
|
68
|
+
const target = toUrl(baseUrl, pathOrUrl);
|
|
69
|
+
return await fetch(target, init);
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
context,
|
|
74
|
+
async stop() {
|
|
75
|
+
await stopProcess(child);
|
|
76
|
+
},
|
|
77
|
+
};
|
|
63
78
|
}
|
|
64
79
|
export function backendTest(name, callback) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
80
|
+
const globalTest = globalThis.test;
|
|
81
|
+
if (typeof globalTest !== 'function') {
|
|
82
|
+
throw new Error('backendTest() requires the @webstir-io/webstir-testing runtime.');
|
|
83
|
+
}
|
|
84
|
+
globalTest(name, async () => {
|
|
85
|
+
const context = getBackendTestContext();
|
|
86
|
+
if (!context) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
'Backend test context not available. Ensure backend tests run via the Webstir CLI (`webstir test`).',
|
|
89
|
+
);
|
|
68
90
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (!context) {
|
|
72
|
-
throw new Error('Backend test context not available. Ensure backend tests run via the Webstir CLI (`webstir test`).');
|
|
73
|
-
}
|
|
74
|
-
await callback(context);
|
|
75
|
-
});
|
|
91
|
+
await callback(context);
|
|
92
|
+
});
|
|
76
93
|
}
|
|
77
94
|
function toUrl(base, pathOrUrl) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
95
|
+
if (pathOrUrl instanceof URL) {
|
|
96
|
+
return pathOrUrl.toString();
|
|
97
|
+
}
|
|
98
|
+
if (/^https?:/i.test(pathOrUrl)) {
|
|
99
|
+
return pathOrUrl;
|
|
100
|
+
}
|
|
101
|
+
return new URL(pathOrUrl, base).toString();
|
|
85
102
|
}
|
|
86
103
|
function readInt(value, fallback) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
104
|
+
if (!value) return fallback;
|
|
105
|
+
const parsed = Number.parseInt(value, 10);
|
|
106
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
91
107
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
108
|
+
|
|
109
|
+
async function startBackendTestProcess(options) {
|
|
110
|
+
let candidate = options.requestedPort;
|
|
111
|
+
let lastError = null;
|
|
112
|
+
|
|
113
|
+
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
114
|
+
const env = createRuntimeEnv({
|
|
115
|
+
workspaceRoot: options.workspaceRoot,
|
|
116
|
+
port: candidate,
|
|
117
|
+
baseEnv: options.baseEnv,
|
|
118
|
+
overrides: options.overrides,
|
|
119
|
+
});
|
|
120
|
+
const child = spawn(process.execPath, [options.entry], {
|
|
121
|
+
cwd: options.workspaceRoot,
|
|
122
|
+
env,
|
|
123
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
124
|
+
});
|
|
125
|
+
const output = captureChildOutput(child);
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
await waitForReady(child, options.readyText, options.readyTimeoutMs);
|
|
129
|
+
output.stop();
|
|
130
|
+
return { child, env, port: candidate };
|
|
131
|
+
} catch (error) {
|
|
132
|
+
const captured = output.read();
|
|
133
|
+
output.stop();
|
|
134
|
+
await stopProcess(child);
|
|
135
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
136
|
+
const failure = new Error(
|
|
137
|
+
`Backend test server did not become ready on port ${candidate}.\nstdout:\n${captured.stdout}\nstderr:\n${captured.stderr}\nerror:\n${message}`,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
if (attempt < 9 && indicatesPortInUse(captured.stdout, captured.stderr, message)) {
|
|
141
|
+
lastError = failure;
|
|
99
142
|
candidate += 1;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
throw failure;
|
|
100
147
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return new Promise((resolve) => {
|
|
105
|
-
const server = net.createServer();
|
|
106
|
-
server.once('error', () => {
|
|
107
|
-
server.close(() => resolve(false));
|
|
108
|
-
});
|
|
109
|
-
server.once('listening', () => {
|
|
110
|
-
server.close(() => resolve(true));
|
|
111
|
-
});
|
|
112
|
-
server.listen(port, '127.0.0.1');
|
|
113
|
-
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
throw lastError ?? new Error('Backend test server did not become ready.');
|
|
114
151
|
}
|
|
115
152
|
function createRuntimeEnv(options) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
153
|
+
const overrides = {};
|
|
154
|
+
for (const [key, value] of Object.entries(options.overrides ?? {})) {
|
|
155
|
+
if (value !== undefined) {
|
|
156
|
+
overrides[key] = value;
|
|
121
157
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
158
|
+
}
|
|
159
|
+
const baseUrl =
|
|
160
|
+
overrides.API_BASE_URL ?? options.baseEnv.API_BASE_URL ?? `http://127.0.0.1:${options.port}`;
|
|
161
|
+
return {
|
|
162
|
+
...options.baseEnv,
|
|
163
|
+
...overrides,
|
|
164
|
+
PORT: String(options.port),
|
|
165
|
+
API_BASE_URL: baseUrl,
|
|
166
|
+
NODE_ENV: overrides.NODE_ENV ?? options.baseEnv.NODE_ENV ?? 'test',
|
|
167
|
+
WORKSPACE_ROOT: options.workspaceRoot,
|
|
168
|
+
WEBSTIR_BACKEND_TEST_RUN: '1',
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function captureChildOutput(child) {
|
|
173
|
+
let stdout = '';
|
|
174
|
+
let stderr = '';
|
|
175
|
+
|
|
176
|
+
const onStdout = (chunk) => {
|
|
177
|
+
stdout += chunk.toString();
|
|
178
|
+
};
|
|
179
|
+
const onStderr = (chunk) => {
|
|
180
|
+
stderr += chunk.toString();
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
child.stdout?.on('data', onStdout);
|
|
184
|
+
child.stderr?.on('data', onStderr);
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
stop() {
|
|
188
|
+
child.stdout?.off('data', onStdout);
|
|
189
|
+
child.stderr?.off('data', onStderr);
|
|
190
|
+
},
|
|
191
|
+
read() {
|
|
192
|
+
return { stdout, stderr };
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function indicatesPortInUse(stdout, stderr, message) {
|
|
198
|
+
return [stdout, stderr, message].some(
|
|
199
|
+
(value) =>
|
|
200
|
+
value.includes('EADDRINUSE') ||
|
|
201
|
+
value.includes('address already in use') ||
|
|
202
|
+
value.includes('Failed to listen at 127.0.0.1'),
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function resolveWorkspacePath(workspaceRoot, value) {
|
|
207
|
+
return path.isAbsolute(value) ? path.resolve(value) : path.resolve(workspaceRoot, value);
|
|
132
208
|
}
|
|
133
209
|
async function loadManifest(manifestPath) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
210
|
+
try {
|
|
211
|
+
const raw = await readFile(manifestPath, 'utf8');
|
|
212
|
+
return JSON.parse(raw);
|
|
213
|
+
} catch {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
141
216
|
}
|
|
142
217
|
function emitModuleEvent(level, message) {
|
|
143
|
-
|
|
144
|
-
|
|
218
|
+
const payload = JSON.stringify({ type: level, message });
|
|
219
|
+
process.stdout.write(`WEBSTIR_MODULE_EVENT ${payload}\n`);
|
|
145
220
|
}
|
|
146
221
|
async function waitForReady(child, readyText, timeoutMs) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
222
|
+
const normalized = readyText
|
|
223
|
+
.split('|')
|
|
224
|
+
.map((token) => token.trim())
|
|
225
|
+
.filter(Boolean);
|
|
226
|
+
const readinessMatches = (line) =>
|
|
227
|
+
normalized.length === 0 ? line.length > 0 : normalized.some((token) => line.includes(token));
|
|
228
|
+
await new Promise((resolve, reject) => {
|
|
229
|
+
const cleanup = () => {
|
|
230
|
+
child.stdout?.off('data', onStdout);
|
|
231
|
+
child.stderr?.off('data', onStderr);
|
|
232
|
+
child.off('exit', onExit);
|
|
233
|
+
clearTimeout(timer);
|
|
234
|
+
};
|
|
235
|
+
const onStdout = (chunk) => {
|
|
236
|
+
const text = chunk.toString();
|
|
237
|
+
for (const line of text.split(/\r?\n/)) {
|
|
238
|
+
if (!line) continue;
|
|
239
|
+
emitModuleEvent('info', line);
|
|
240
|
+
if (readinessMatches(line)) {
|
|
241
|
+
cleanup();
|
|
242
|
+
resolve();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
const onStderr = (chunk) => {
|
|
247
|
+
const text = chunk.toString();
|
|
248
|
+
for (const line of text.split(/\r?\n/)) {
|
|
249
|
+
if (!line) continue;
|
|
250
|
+
emitModuleEvent('error', line);
|
|
251
|
+
if (readinessMatches(line)) {
|
|
252
|
+
cleanup();
|
|
253
|
+
resolve();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
const onExit = (code) => {
|
|
258
|
+
cleanup();
|
|
259
|
+
reject(
|
|
260
|
+
new Error(`Backend test server exited before it became ready (code ${code ?? 'null'}).`),
|
|
261
|
+
);
|
|
262
|
+
};
|
|
263
|
+
const timer = setTimeout(() => {
|
|
264
|
+
cleanup();
|
|
265
|
+
emitModuleEvent('error', 'Backend test server readiness timed out.');
|
|
266
|
+
reject(
|
|
267
|
+
new Error(
|
|
268
|
+
`Backend test server did not become ready within ${timeoutMs}ms. Check server logs for details.`,
|
|
269
|
+
),
|
|
270
|
+
);
|
|
271
|
+
}, timeoutMs);
|
|
272
|
+
child.stdout?.on('data', onStdout);
|
|
273
|
+
child.stderr?.on('data', onStderr);
|
|
274
|
+
child.once('exit', onExit);
|
|
275
|
+
});
|
|
196
276
|
}
|
|
197
277
|
async function stopProcess(child) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
278
|
+
if (!child || child.killed || child.exitCode !== null) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
child.kill('SIGTERM');
|
|
282
|
+
try {
|
|
283
|
+
await once(child, 'exit');
|
|
284
|
+
} catch {
|
|
285
|
+
// ignore
|
|
286
|
+
}
|
|
208
287
|
}
|