-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathserve-process.ts
More file actions
101 lines (90 loc) · 3.29 KB
/
Copy pathserve-process.ts
File metadata and controls
101 lines (90 loc) · 3.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
/**
* Shared harness for e2e tests that need the REAL `os serve` process.
*
* Some serve defects only exist above the kernel — the boot-quiet stdout window
* (#4012), the plugin registration ORDER the command assembles (#4085) — so
* they survive every in-process test and only a test that spawns the actual
* command can catch them. This module owns that spawn so each e2e file asserts
* rather than re-implements it.
*/
import { spawn } from 'node:child_process';
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const HERE = resolve(fileURLToPath(import.meta.url), '..');
/** `bin/run-dev.js` — the CLI entrypoint that runs from TS source via tsx. */
export const CLI = resolve(HERE, '../../bin/run-dev.js');
export const TSX = resolve(HERE, '../../../../node_modules/.bin/tsx');
/** A random high port, so a run never contends with a dev server on this host. */
export function randomPort(): string {
return String(40000 + Math.floor(Math.random() * 20000));
}
export interface ServeRun {
stdout: string;
stderr: string;
}
/**
* Boot `os serve` in `cwd`, collect its output until `waitFor` matches (or the
* process exits), then stop it. Never leaves the child running.
*
* A boot that DIES still has to have said why, so an early exit resolves rather
* than rejects — the caller's assertions read what it printed on the way down.
*/
export function runServe(
cwd: string,
args: string[],
// `env` values may be `undefined` to UNSET a variable for the child (Node
// omits undefined entries), which is how a test asserts behaviour that depends
// on a variable being absent — `''` would not do it, since the resolvers this
// exercises use `??` and an empty string is not nullish.
opts: { waitFor: RegExp; timeoutMs?: number; config?: string; env?: Record<string, string | undefined> },
): Promise<ServeRun> {
return new Promise((resolveRun, rejectRun) => {
const child = spawn(TSX, [CLI, 'serve', opts.config ?? 'objectstack.config.ts', ...args], {
cwd,
env: {
...process.env,
NO_COLOR: '1',
// Keep the fixture self-contained: no file written, no port conflict
// with another agent's dev server, no inherited log level.
OS_DATABASE_URL: ':memory:',
OS_LOG_LEVEL: '',
OS_DISABLE_CONSOLE: '1',
...(opts.env ?? {}),
},
});
let stdout = '';
let stderr = '';
let settled = false;
const finish = (err?: Error) => {
if (settled) return;
settled = true;
clearTimeout(timer);
try {
child.kill('SIGTERM');
} catch {
/* already gone */
}
if (err) rejectRun(err);
else resolveRun({ stdout, stderr });
};
const timer = setTimeout(
() =>
finish(
new Error(
`serve did not reach ${opts.waitFor} in time.\n--- stdout ---\n${stdout}\n--- stderr ---\n${stderr}`,
),
),
opts.timeoutMs ?? 180_000,
);
child.stdout.on('data', (d) => {
stdout += String(d);
if (opts.waitFor.test(stdout)) finish();
});
child.stderr.on('data', (d) => {
stderr += String(d);
});
child.on('error', (err) => finish(err));
child.on('exit', () => finish());
});
}