@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.
Files changed (123) hide show
  1. package/README.md +106 -79
  2. package/dist/add.d.ts +59 -0
  3. package/dist/add.js +626 -0
  4. package/dist/build/artifacts.d.ts +115 -1
  5. package/dist/build/artifacts.js +4 -4
  6. package/dist/build/entries.js +1 -1
  7. package/dist/build/pipeline.d.ts +33 -1
  8. package/dist/build/pipeline.js +307 -65
  9. package/dist/cache/diff.js +9 -8
  10. package/dist/cache/reporters.js +1 -1
  11. package/dist/deploy-cli.d.ts +2 -0
  12. package/dist/deploy-cli.js +86 -0
  13. package/dist/diagnostics/summary.js +2 -2
  14. package/dist/index.d.ts +6 -0
  15. package/dist/index.js +4 -0
  16. package/dist/manifest/pipeline.js +103 -32
  17. package/dist/provider.js +35 -17
  18. package/dist/runtime/bun.d.ts +51 -0
  19. package/dist/runtime/bun.js +499 -0
  20. package/dist/runtime/core.d.ts +141 -0
  21. package/dist/runtime/core.js +316 -0
  22. package/dist/runtime/deploy-backend.d.ts +20 -0
  23. package/dist/runtime/deploy-backend.js +175 -0
  24. package/dist/runtime/deploy-shared.d.ts +43 -0
  25. package/dist/runtime/deploy-shared.js +75 -0
  26. package/dist/runtime/deploy-static.d.ts +2 -0
  27. package/dist/runtime/deploy-static.js +161 -0
  28. package/dist/runtime/deploy.d.ts +3 -0
  29. package/dist/runtime/deploy.js +91 -0
  30. package/dist/runtime/forms.d.ts +73 -0
  31. package/dist/runtime/forms.js +236 -0
  32. package/dist/runtime/request-hooks.d.ts +47 -0
  33. package/dist/runtime/request-hooks.js +102 -0
  34. package/dist/runtime/session-metadata.d.ts +13 -0
  35. package/dist/runtime/session-metadata.js +98 -0
  36. package/dist/runtime/session-runtime.d.ts +28 -0
  37. package/dist/runtime/session-runtime.js +180 -0
  38. package/dist/runtime/session.d.ts +83 -0
  39. package/dist/runtime/session.js +396 -0
  40. package/dist/runtime/views.d.ts +74 -0
  41. package/dist/runtime/views.js +221 -0
  42. package/dist/scaffold/assets.js +25 -21
  43. package/dist/testing/context.js +1 -1
  44. package/dist/testing/index.d.ts +1 -1
  45. package/dist/testing/index.js +100 -56
  46. package/dist/utils/bun.d.ts +2 -0
  47. package/dist/utils/bun.js +13 -0
  48. package/dist/watch.d.ts +13 -1
  49. package/dist/watch.js +345 -97
  50. package/dist/workspace.d.ts +8 -0
  51. package/dist/workspace.js +44 -3
  52. package/package.json +49 -14
  53. package/scripts/publish.sh +2 -92
  54. package/scripts/smoke.mjs +282 -107
  55. package/scripts/update-contract.sh +12 -10
  56. package/src/add.ts +964 -0
  57. package/src/build/artifacts.ts +49 -46
  58. package/src/build/entries.ts +12 -12
  59. package/src/build/pipeline.ts +779 -403
  60. package/src/cache/diff.ts +111 -105
  61. package/src/cache/reporters.ts +26 -26
  62. package/src/deploy-cli.ts +111 -0
  63. package/src/diagnostics/summary.ts +28 -22
  64. package/src/index.ts +11 -0
  65. package/src/manifest/pipeline.ts +328 -215
  66. package/src/provider.ts +115 -98
  67. package/src/runtime/bun.ts +793 -0
  68. package/src/runtime/core.ts +598 -0
  69. package/src/runtime/deploy-backend.ts +239 -0
  70. package/src/runtime/deploy-shared.ts +136 -0
  71. package/src/runtime/deploy-static.ts +191 -0
  72. package/src/runtime/deploy.ts +143 -0
  73. package/src/runtime/forms.ts +364 -0
  74. package/src/runtime/request-hooks.ts +165 -0
  75. package/src/runtime/session-metadata.ts +135 -0
  76. package/src/runtime/session-runtime.ts +267 -0
  77. package/src/runtime/session.ts +642 -0
  78. package/src/runtime/views.ts +385 -0
  79. package/src/scaffold/assets.ts +77 -73
  80. package/src/testing/context.js +8 -9
  81. package/src/testing/context.ts +9 -9
  82. package/src/testing/index.d.ts +14 -3
  83. package/src/testing/index.js +254 -175
  84. package/src/testing/index.ts +298 -195
  85. package/src/testing/types.d.ts +18 -19
  86. package/src/testing/types.ts +18 -18
  87. package/src/utils/bun.ts +26 -0
  88. package/src/watch.ts +503 -99
  89. package/src/workspace.ts +59 -3
  90. package/templates/backend/.env.example +15 -0
  91. package/templates/backend/auth/adapter.ts +335 -36
  92. package/templates/backend/db/connection.ts +190 -65
  93. package/templates/backend/db/migrate.ts +149 -43
  94. package/templates/backend/db/types.d.ts +1 -1
  95. package/templates/backend/env.ts +132 -20
  96. package/templates/backend/functions/hello/index.ts +1 -2
  97. package/templates/backend/index.ts +15 -508
  98. package/templates/backend/jobs/nightly/index.ts +1 -1
  99. package/templates/backend/jobs/runtime.ts +24 -11
  100. package/templates/backend/jobs/scheduler.ts +208 -46
  101. package/templates/backend/module.ts +227 -13
  102. package/templates/backend/observability/logger.ts +2 -12
  103. package/templates/backend/observability/metrics.ts +8 -5
  104. package/templates/backend/session/sqlite.ts +152 -0
  105. package/templates/backend/session/store.ts +45 -0
  106. package/templates/backend/tsconfig.json +1 -1
  107. package/tests/add.test.js +327 -0
  108. package/tests/authAdapter.test.js +315 -0
  109. package/tests/bundlerParity.test.js +217 -0
  110. package/tests/cacheReporter.test.js +10 -10
  111. package/tests/dbConnection.test.js +209 -0
  112. package/tests/deploy.test.js +357 -0
  113. package/tests/envLoader.test.js +271 -17
  114. package/tests/integration.test.js +2432 -3
  115. package/tests/jobsScheduler.test.js +253 -0
  116. package/tests/manifest.test.js +287 -12
  117. package/tests/migrationRunner.test.js +249 -0
  118. package/tests/sessionScaffoldStore.test.js +752 -0
  119. package/tests/sessionStore.test.js +490 -0
  120. package/tests/testing.test.js +252 -0
  121. package/tests/watch.test.js +192 -32
  122. package/tsconfig.json +3 -10
  123. package/templates/backend/server/fastify.ts +0 -288
@@ -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
- const workspaceRoot = options.workspaceRoot ?? process.env.WEBSTIR_WORKSPACE_ROOT ?? process.cwd();
14
- const buildRoot = options.buildRoot ?? process.env.WEBSTIR_BACKEND_BUILD_ROOT ?? path.join(workspaceRoot, 'build', 'backend');
15
- const entry = options.entry ?? process.env.WEBSTIR_BACKEND_TEST_ENTRY ?? path.join(buildRoot, 'index.js');
16
- const manifestPath = options.manifestPath ??
17
- process.env.WEBSTIR_BACKEND_TEST_MANIFEST ??
18
- path.join(workspaceRoot, '.webstir', 'backend-manifest.json');
19
- const readyText = options.readyText ?? process.env.WEBSTIR_BACKEND_TEST_READY ?? DEFAULT_READY_TEXT;
20
- const readyTimeoutMs = options.readyTimeoutMs ?? readInt(process.env.WEBSTIR_BACKEND_TEST_READY_TIMEOUT, DEFAULT_READY_TIMEOUT_MS);
21
- if (!existsSync(entry)) {
22
- throw new Error(`Backend test entry not found at ${entry}. Run the backend build before executing backend tests.`);
23
- }
24
- const requestedPort = options.port ?? readInt(process.env.WEBSTIR_BACKEND_TEST_PORT, DEFAULT_PORT);
25
- const port = await findOpenPort(requestedPort);
26
- const env = createRuntimeEnv({
27
- workspaceRoot,
28
- port,
29
- overrides: options.env
30
- });
31
- const manifest = await loadManifest(manifestPath);
32
- const child = spawn(process.execPath, [entry], {
33
- cwd: workspaceRoot,
34
- env,
35
- stdio: ['ignore', 'pipe', 'pipe']
36
- });
37
- try {
38
- await waitForReady(child, readyText, readyTimeoutMs);
39
- }
40
- catch (error) {
41
- await stopProcess(child);
42
- throw error;
43
- }
44
- const baseUrl = new URL(env.API_BASE_URL ?? `http://127.0.0.1:${port}`);
45
- const context = {
46
- baseUrl: baseUrl.toString(),
47
- url: baseUrl,
48
- port,
49
- manifest,
50
- routes: Array.isArray(manifest?.routes) ? manifest.routes : [],
51
- env,
52
- request: async (pathOrUrl = '/', init) => {
53
- const target = toUrl(baseUrl, pathOrUrl);
54
- return await fetch(target, init);
55
- }
56
- };
57
- return {
58
- context,
59
- async stop() {
60
- await stopProcess(child);
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
- const globalTest = globalThis.test;
66
- if (typeof globalTest !== 'function') {
67
- throw new Error('backendTest() requires the @webstir-io/webstir-testing runtime.');
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
- globalTest(name, async () => {
70
- const context = getBackendTestContext();
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
- if (pathOrUrl instanceof URL) {
79
- return pathOrUrl.toString();
80
- }
81
- if (/^https?:/i.test(pathOrUrl)) {
82
- return pathOrUrl;
83
- }
84
- return new URL(pathOrUrl, base).toString();
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
- if (!value)
88
- return fallback;
89
- const parsed = Number.parseInt(value, 10);
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
- async function findOpenPort(start, attempts = 10) {
93
- let candidate = start;
94
- for (let index = 0; index < attempts; index += 1) {
95
- // eslint-disable-next-line no-await-in-loop
96
- if (await isPortAvailable(candidate)) {
97
- return candidate;
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
- throw new Error(`Unable to find an open port for backend tests (tried starting at ${start}).`);
102
- }
103
- function isPortAvailable(port) {
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
- const overrides = {};
117
- for (const [key, value] of Object.entries(options.overrides ?? {})) {
118
- if (value !== undefined) {
119
- overrides[key] = value;
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
- const baseUrl = overrides.API_BASE_URL ?? process.env.API_BASE_URL ?? `http://127.0.0.1:${options.port}`;
123
- return {
124
- ...process.env,
125
- ...overrides,
126
- PORT: String(options.port),
127
- API_BASE_URL: baseUrl,
128
- NODE_ENV: overrides.NODE_ENV ?? process.env.NODE_ENV ?? 'test',
129
- WORKSPACE_ROOT: options.workspaceRoot,
130
- WEBSTIR_BACKEND_TEST_RUN: '1'
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
- try {
135
- const raw = await readFile(manifestPath, 'utf8');
136
- return JSON.parse(raw);
137
- }
138
- catch {
139
- return null;
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
- const payload = JSON.stringify({ type: level, message });
144
- process.stdout.write(`WEBSTIR_MODULE_EVENT ${payload}\n`);
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
- const normalized = readyText
148
- .split('|')
149
- .map((token) => token.trim())
150
- .filter(Boolean);
151
- const readinessMatches = (line) => (normalized.length === 0 ? line.length > 0 : normalized.some((token) => line.includes(token)));
152
- await new Promise((resolve, reject) => {
153
- const cleanup = () => {
154
- child.stdout?.off('data', onStdout);
155
- child.stderr?.off('data', onStderr);
156
- child.off('exit', onExit);
157
- clearTimeout(timer);
158
- };
159
- const onStdout = (chunk) => {
160
- const text = chunk.toString();
161
- for (const line of text.split(/\r?\n/)) {
162
- if (!line)
163
- continue;
164
- emitModuleEvent('info', line);
165
- if (readinessMatches(line)) {
166
- cleanup();
167
- resolve();
168
- }
169
- }
170
- };
171
- const onStderr = (chunk) => {
172
- const text = chunk.toString();
173
- for (const line of text.split(/\r?\n/)) {
174
- if (!line)
175
- continue;
176
- emitModuleEvent('error', line);
177
- if (readinessMatches(line)) {
178
- cleanup();
179
- resolve();
180
- }
181
- }
182
- };
183
- const onExit = (code) => {
184
- cleanup();
185
- reject(new Error(`Backend test server exited before it became ready (code ${code ?? 'null'}).`));
186
- };
187
- const timer = setTimeout(() => {
188
- cleanup();
189
- emitModuleEvent('error', 'Backend test server readiness timed out.');
190
- reject(new Error(`Backend test server did not become ready within ${timeoutMs}ms. Check server logs for details.`));
191
- }, timeoutMs);
192
- child.stdout?.on('data', onStdout);
193
- child.stderr?.on('data', onStderr);
194
- child.once('exit', onExit);
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
- if (!child || child.killed || child.exitCode !== null) {
199
- return;
200
- }
201
- child.kill('SIGTERM');
202
- try {
203
- await once(child, 'exit');
204
- }
205
- catch {
206
- // ignore
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
  }