@gnuechtel/nx-cucumber 2.0.0-beta.1 → 2.0.0-beta.2
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/package.json +3 -3
- package/src/core/web-server.d.ts +12 -1
- package/src/core/web-server.js +27 -29
- package/src/core/web-server.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gnuechtel/nx-cucumber",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.2",
|
|
4
4
|
"description": "Plugin to use Cucumber within Nx workspaces",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"@nx/devkit": ">=22 <23",
|
|
13
13
|
"minimatch": "^10.0.0",
|
|
14
14
|
"tslib": "^2.3.0",
|
|
15
|
-
"@gnuechtel/shared-coverage": "2.0.0-beta.
|
|
16
|
-
"@gnuechtel/shared-util": "2.0.0-beta.
|
|
15
|
+
"@gnuechtel/shared-coverage": "2.0.0-beta.2",
|
|
16
|
+
"@gnuechtel/shared-util": "2.0.0-beta.2"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
19
|
"@cucumber/cucumber": ">=9 <13",
|
package/src/core/web-server.d.ts
CHANGED
|
@@ -3,13 +3,24 @@ export interface WebServerConfig {
|
|
|
3
3
|
url: string;
|
|
4
4
|
reuseExistingServer?: boolean;
|
|
5
5
|
timeout?: number;
|
|
6
|
+
/** Working directory for the server process. Defaults to the current working directory. */
|
|
7
|
+
cwd?: string;
|
|
8
|
+
/** Environment variables for the server process. Defaults to the current process environment. */
|
|
9
|
+
env?: NodeJS.ProcessEnv;
|
|
10
|
+
/**
|
|
11
|
+
* stdio mode for the server process.
|
|
12
|
+
* Use 'pipe' (default) to forward server output prefixed with '[serve]'.
|
|
13
|
+
* Use 'ignore' when starting the server inside a spawnSync context to avoid pipe-buffer deadlocks.
|
|
14
|
+
*/
|
|
15
|
+
stdio?: 'pipe' | 'ignore';
|
|
6
16
|
}
|
|
7
17
|
/**
|
|
8
18
|
* Starts a web server before Cucumber runs and stops it when the process exits.
|
|
9
19
|
* Mirrors Playwright's webServer config pattern.
|
|
10
20
|
*
|
|
11
21
|
* Note: when running inside a parent `spawnSync` context (e.g., nyc coverage wrapper),
|
|
12
|
-
*
|
|
22
|
+
* set stdio to 'ignore' to avoid pipe-buffer deadlocks, or start the server externally
|
|
23
|
+
* before invoking cucumber and rely on `reuseExistingServer`.
|
|
13
24
|
*/
|
|
14
25
|
export declare function startWebServer(config: WebServerConfig): void;
|
|
15
26
|
export declare function stopWebServer(): void;
|
package/src/core/web-server.js
CHANGED
|
@@ -3,16 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.startWebServer = startWebServer;
|
|
4
4
|
exports.stopWebServer = stopWebServer;
|
|
5
5
|
const child_process_1 = require("child_process");
|
|
6
|
+
const shared_util_1 = require("@gnuechtel/shared-util");
|
|
6
7
|
let serverProcess;
|
|
7
8
|
/**
|
|
8
9
|
* Starts a web server before Cucumber runs and stops it when the process exits.
|
|
9
10
|
* Mirrors Playwright's webServer config pattern.
|
|
10
11
|
*
|
|
11
12
|
* Note: when running inside a parent `spawnSync` context (e.g., nyc coverage wrapper),
|
|
12
|
-
*
|
|
13
|
+
* set stdio to 'ignore' to avoid pipe-buffer deadlocks, or start the server externally
|
|
14
|
+
* before invoking cucumber and rely on `reuseExistingServer`.
|
|
13
15
|
*/
|
|
14
16
|
function startWebServer(config) {
|
|
15
|
-
const { command, url, reuseExistingServer = true, timeout = 60000 } = config;
|
|
17
|
+
const { command, url, reuseExistingServer = true, timeout = 60000, cwd, env, stdio = 'pipe' } = config;
|
|
16
18
|
if (reuseExistingServer && isUrlReachable(url)) {
|
|
17
19
|
console.log(`Reusing existing server at ${url}`);
|
|
18
20
|
return;
|
|
@@ -20,15 +22,19 @@ function startWebServer(config) {
|
|
|
20
22
|
console.log(`Starting web server: ${command}`);
|
|
21
23
|
serverProcess = (0, child_process_1.spawn)(command, {
|
|
22
24
|
shell: true,
|
|
23
|
-
stdio
|
|
25
|
+
stdio,
|
|
24
26
|
detached: true,
|
|
27
|
+
...(cwd ? { cwd } : {}),
|
|
28
|
+
...(env ? { env } : {}),
|
|
25
29
|
});
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
if (stdio === 'pipe') {
|
|
31
|
+
serverProcess.stdout?.on('data', (data) => {
|
|
32
|
+
process.stdout.write(`[serve] ${data}`);
|
|
33
|
+
});
|
|
34
|
+
serverProcess.stderr?.on('data', (data) => {
|
|
35
|
+
process.stderr.write(`[serve] ${data}`);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
32
38
|
serverProcess.unref();
|
|
33
39
|
process.on('exit', stopWebServer);
|
|
34
40
|
process.on('SIGINT', stopWebServer);
|
|
@@ -36,31 +42,23 @@ function startWebServer(config) {
|
|
|
36
42
|
console.log(`Waiting for ${url}...`);
|
|
37
43
|
waitForUrl(url, timeout);
|
|
38
44
|
console.log(`Server ready at ${url}`);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
if (stdio === 'pipe') {
|
|
46
|
+
// Unref the stdio streams so they don't prevent the parent process from exiting
|
|
47
|
+
// once the tests complete. The streams are still active and will forward output,
|
|
48
|
+
// but the event loop won't be kept alive waiting for them.
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
+
serverProcess.stdout?.unref?.();
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
52
|
+
serverProcess.stderr?.unref?.();
|
|
53
|
+
}
|
|
46
54
|
}
|
|
47
55
|
function stopWebServer() {
|
|
48
56
|
if (!serverProcess || serverProcess.killed)
|
|
49
57
|
return;
|
|
50
|
-
|
|
51
|
-
if (serverProcess.pid) {
|
|
52
|
-
process.kill(-serverProcess.pid, 'SIGTERM');
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
try {
|
|
57
|
-
serverProcess.kill();
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
// ignore
|
|
61
|
-
}
|
|
62
|
-
}
|
|
58
|
+
const pid = serverProcess.pid;
|
|
63
59
|
serverProcess = undefined;
|
|
60
|
+
if (pid)
|
|
61
|
+
(0, shared_util_1.killProcessTree)(pid);
|
|
64
62
|
}
|
|
65
63
|
function waitForUrl(url, timeout) {
|
|
66
64
|
const interval = 1000;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-server.js","sourceRoot":"","sources":["../../../../../libs/nx-cucumber/src/core/web-server.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"web-server.js","sourceRoot":"","sources":["../../../../../libs/nx-cucumber/src/core/web-server.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync, spawn } from 'child_process';\nimport { killProcessTree } from '@gnuechtel/shared-util';\n\nexport interface WebServerConfig {\n command: string;\n url: string;\n reuseExistingServer?: boolean;\n timeout?: number;\n /** Working directory for the server process. Defaults to the current working directory. */\n cwd?: string;\n /** Environment variables for the server process. Defaults to the current process environment. */\n env?: NodeJS.ProcessEnv;\n /**\n * stdio mode for the server process.\n * Use 'pipe' (default) to forward server output prefixed with '[serve]'.\n * Use 'ignore' when starting the server inside a spawnSync context to avoid pipe-buffer deadlocks.\n */\n stdio?: 'pipe' | 'ignore';\n}\n\nlet serverProcess: ReturnType<typeof spawn> | undefined;\n\n/**\n * Starts a web server before Cucumber runs and stops it when the process exits.\n * Mirrors Playwright's webServer config pattern.\n *\n * Note: when running inside a parent `spawnSync` context (e.g., nyc coverage wrapper),\n * set stdio to 'ignore' to avoid pipe-buffer deadlocks, or start the server externally\n * before invoking cucumber and rely on `reuseExistingServer`.\n */\nexport function startWebServer(config: WebServerConfig): void {\n const { command, url, reuseExistingServer = true, timeout = 60000, cwd, env, stdio = 'pipe' } = config;\n\n if (reuseExistingServer && isUrlReachable(url)) {\n console.log(`Reusing existing server at ${url}`);\n return;\n }\n\n console.log(`Starting web server: ${command}`);\n serverProcess = spawn(command, {\n shell: true,\n stdio,\n detached: true,\n ...(cwd ? { cwd } : {}),\n ...(env ? { env } : {}),\n });\n\n if (stdio === 'pipe') {\n serverProcess.stdout?.on('data', (data: Buffer) => {\n process.stdout.write(`[serve] ${data}`);\n });\n serverProcess.stderr?.on('data', (data: Buffer) => {\n process.stderr.write(`[serve] ${data}`);\n });\n }\n\n serverProcess.unref();\n\n process.on('exit', stopWebServer);\n process.on('SIGINT', stopWebServer);\n process.on('SIGTERM', stopWebServer);\n\n console.log(`Waiting for ${url}...`);\n waitForUrl(url, timeout);\n console.log(`Server ready at ${url}`);\n\n if (stdio === 'pipe') {\n // Unref the stdio streams so they don't prevent the parent process from exiting\n // once the tests complete. The streams are still active and will forward output,\n // but the event loop won't be kept alive waiting for them.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (serverProcess.stdout as any)?.unref?.();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (serverProcess.stderr as any)?.unref?.();\n }\n}\n\nexport function stopWebServer(): void {\n if (!serverProcess || serverProcess.killed) return;\n const pid = serverProcess.pid;\n serverProcess = undefined;\n if (pid) killProcessTree(pid);\n}\n\nfunction waitForUrl(url: string, timeout: number): void {\n const interval = 1000;\n const deadline = Date.now() + timeout;\n while (Date.now() < deadline) {\n if (isUrlReachable(url)) return;\n if (serverProcess?.exitCode !== null && serverProcess?.exitCode !== undefined) {\n throw new Error(`Web server exited with code ${serverProcess.exitCode} before becoming ready`);\n }\n sleepSync(interval);\n }\n throw new Error(`Timed out after ${timeout}ms waiting for ${url}`);\n}\n\nfunction sleepSync(ms: number): void {\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);\n}\n\nfunction isUrlReachable(url: string): boolean {\n try {\n execSync(`node -e \"fetch('${url}').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))\"`, {\n stdio: 'ignore',\n timeout: 3000,\n });\n return true;\n } catch {\n return false;\n }\n}\n"]}
|