@gnuechtel/nx-cucumber 2.0.0-beta.4 → 2.0.0
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 +130 -49
- package/migrations.json +1 -1
- package/package.json +3 -9
- package/src/adapters/registry.js +6 -1
- package/src/adapters/registry.js.map +1 -1
- package/src/core/cucumber-run-metadata-formatter.js +111 -0
- package/src/generators/project/files/all/cucumber.js.template +17 -5
- package/src/generators/project/files/playwright/run-e2e.js.template +112 -0
- package/src/generators/project/project.js +72 -29
- package/src/generators/project/project.js.map +1 -1
- package/src/index.d.ts +0 -4
- package/src/index.js +1 -6
- package/src/index.js.map +1 -1
- package/src/migrations/update-2-0-0/migrate-cucumber-api-to-inferred-tasks.js +24 -8
- package/src/migrations/update-2-0-0/migrate-cucumber-api-to-inferred-tasks.js.map +1 -1
- package/src/plugin/create-nodes.d.ts +14 -0
- package/src/plugin/create-nodes.js +4 -0
- package/src/plugin/create-nodes.js.map +1 -1
- package/src/core/cucumber-report-html.d.ts +0 -11
- package/src/core/cucumber-report-html.js +0 -58
- package/src/core/cucumber-report-html.js.map +0 -1
- package/src/core/web-server.d.ts +0 -29
- package/src/core/web-server.js +0 -139
- package/src/core/web-server.js.map +0 -1
package/src/core/web-server.d.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export interface WebServerConfig {
|
|
2
|
-
command: string;
|
|
3
|
-
url: string;
|
|
4
|
-
reuseExistingServer?: boolean;
|
|
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. Defaults to 'ignore'.
|
|
12
|
-
*
|
|
13
|
-
* Keep the default 'ignore' when calling startWebServer at module load time or from any
|
|
14
|
-
* synchronous context. waitForUrl uses Atomics.wait which blocks the entire Node.js event
|
|
15
|
-
* loop - stdout data handlers cannot fire, so the pipe buffer fills up. On Windows the
|
|
16
|
-
* pipe buffer is only 4 KB: Vite fills it during startup output, blocks on write, and
|
|
17
|
-
* never starts the HTTP server, causing an indefinite hang.
|
|
18
|
-
*
|
|
19
|
-
* Use 'pipe' only when the event loop is free to process I/O during the wait (e.g. when
|
|
20
|
-
* called from an async BeforeAll hook without Atomics.wait).
|
|
21
|
-
*/
|
|
22
|
-
stdio?: 'pipe' | 'ignore';
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Starts a web server before Cucumber runs and stops it when the process exits.
|
|
26
|
-
* Mirrors Playwright's webServer config pattern.
|
|
27
|
-
*/
|
|
28
|
-
export declare function startWebServer(config: WebServerConfig): void;
|
|
29
|
-
export declare function stopWebServer(): void;
|
package/src/core/web-server.js
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.startWebServer = startWebServer;
|
|
4
|
-
exports.stopWebServer = stopWebServer;
|
|
5
|
-
const child_process_1 = require("child_process");
|
|
6
|
-
const shared_util_1 = require("@gnuechtel/shared-util");
|
|
7
|
-
let serverProcess;
|
|
8
|
-
let serverPort;
|
|
9
|
-
/**
|
|
10
|
-
* Starts a web server before Cucumber runs and stops it when the process exits.
|
|
11
|
-
* Mirrors Playwright's webServer config pattern.
|
|
12
|
-
*/
|
|
13
|
-
function startWebServer(config) {
|
|
14
|
-
const { command, url, reuseExistingServer = true, timeout = 60000, cwd, env, stdio = 'ignore' } = config;
|
|
15
|
-
if (reuseExistingServer && isUrlReachable(url)) {
|
|
16
|
-
console.log(`Reusing existing server at ${url}`);
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
serverPort = extractPort(url);
|
|
20
|
-
console.log(`Starting web server: ${command}`);
|
|
21
|
-
serverProcess = (0, child_process_1.spawn)(command, {
|
|
22
|
-
shell: true,
|
|
23
|
-
stdio,
|
|
24
|
-
detached: true,
|
|
25
|
-
...(cwd ? { cwd } : {}),
|
|
26
|
-
...(env ? { env } : {}),
|
|
27
|
-
});
|
|
28
|
-
if (stdio === 'pipe') {
|
|
29
|
-
serverProcess.stdout?.on('data', (data) => {
|
|
30
|
-
process.stdout.write(`[serve] ${data}`);
|
|
31
|
-
});
|
|
32
|
-
serverProcess.stderr?.on('data', (data) => {
|
|
33
|
-
process.stderr.write(`[serve] ${data}`);
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
serverProcess.unref();
|
|
37
|
-
process.on('exit', stopWebServerOnProcessExit);
|
|
38
|
-
process.on('SIGINT', stopWebServer);
|
|
39
|
-
process.on('SIGTERM', stopWebServer);
|
|
40
|
-
console.log(`Waiting for ${url}...`);
|
|
41
|
-
waitForUrl(url, timeout);
|
|
42
|
-
console.log(`Server ready at ${url}`);
|
|
43
|
-
if (stdio === 'pipe') {
|
|
44
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
|
-
serverProcess.stdout?.unref?.();
|
|
46
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
|
-
serverProcess.stderr?.unref?.();
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
function stopWebServer() {
|
|
51
|
-
if (!serverProcess || serverProcess.killed)
|
|
52
|
-
return;
|
|
53
|
-
const pid = serverProcess.pid;
|
|
54
|
-
const port = serverPort;
|
|
55
|
-
serverProcess = undefined;
|
|
56
|
-
serverPort = undefined;
|
|
57
|
-
if (pid)
|
|
58
|
-
(0, shared_util_1.killProcessTree)(pid);
|
|
59
|
-
if (process.platform === 'win32' && port) {
|
|
60
|
-
killProcessOnPort(port);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
function stopWebServerOnProcessExit() {
|
|
64
|
-
if (!serverProcess || serverProcess.killed)
|
|
65
|
-
return;
|
|
66
|
-
const pid = serverProcess.pid;
|
|
67
|
-
serverProcess = undefined;
|
|
68
|
-
serverPort = undefined;
|
|
69
|
-
if (!pid)
|
|
70
|
-
return;
|
|
71
|
-
try {
|
|
72
|
-
// Avoid synchronous process-tree cleanup on process exit to prevent rare
|
|
73
|
-
// Windows deadlocks while the Node.js process is shutting down.
|
|
74
|
-
process.kill(pid, 'SIGTERM');
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
// ignore - process may have already exited
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
function waitForUrl(url, timeout) {
|
|
81
|
-
const interval = 1000;
|
|
82
|
-
const deadline = Date.now() + timeout;
|
|
83
|
-
while (Date.now() < deadline) {
|
|
84
|
-
if (isUrlReachable(url))
|
|
85
|
-
return;
|
|
86
|
-
if (serverProcess?.exitCode !== null && serverProcess?.exitCode !== undefined) {
|
|
87
|
-
throw new Error(`Web server exited with code ${serverProcess.exitCode} before becoming ready`);
|
|
88
|
-
}
|
|
89
|
-
sleepSync(interval);
|
|
90
|
-
}
|
|
91
|
-
throw new Error(`Timed out after ${timeout}ms waiting for ${url}`);
|
|
92
|
-
}
|
|
93
|
-
function sleepSync(ms) {
|
|
94
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
95
|
-
}
|
|
96
|
-
function isUrlReachable(url) {
|
|
97
|
-
if (process.platform === 'win32') {
|
|
98
|
-
return isUrlReachableOnWindows(url);
|
|
99
|
-
}
|
|
100
|
-
// Let the child process exit itself after 2s instead of relying on spawnSync timeout.
|
|
101
|
-
// On Windows this path is avoided because GitLab Windows runners showed rare hangs with
|
|
102
|
-
// node+fetch probes. The PowerShell probe below has a parent timeout as a hard guard.
|
|
103
|
-
const result = (0, child_process_1.spawnSync)('node', [
|
|
104
|
-
'-e',
|
|
105
|
-
`setTimeout(()=>process.exit(1),2000);fetch('${url}').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))`,
|
|
106
|
-
], { stdio: 'ignore' });
|
|
107
|
-
return result.status === 0;
|
|
108
|
-
}
|
|
109
|
-
function isUrlReachableOnWindows(url) {
|
|
110
|
-
const escapedUrl = url.replace(/'/g, "''");
|
|
111
|
-
const result = (0, child_process_1.spawnSync)('powershell', [
|
|
112
|
-
'-NoProfile',
|
|
113
|
-
'-NonInteractive',
|
|
114
|
-
'-Command',
|
|
115
|
-
`$ProgressPreference='SilentlyContinue'; try { $r = Invoke-WebRequest -UseBasicParsing -Uri '${escapedUrl}' -TimeoutSec 2; if ($r.StatusCode -ge 200 -and $r.StatusCode -lt 400) { exit 0 } else { exit 1 } } catch { exit 1 }`,
|
|
116
|
-
], { stdio: 'ignore', timeout: 10000 });
|
|
117
|
-
return result.status === 0;
|
|
118
|
-
}
|
|
119
|
-
function extractPort(url) {
|
|
120
|
-
try {
|
|
121
|
-
const parsed = new URL(url);
|
|
122
|
-
const port = parseInt(parsed.port, 10);
|
|
123
|
-
if (port > 0)
|
|
124
|
-
return port;
|
|
125
|
-
return parsed.protocol === 'https:' ? 443 : 80;
|
|
126
|
-
}
|
|
127
|
-
catch {
|
|
128
|
-
return undefined;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
function killProcessOnPort(port) {
|
|
132
|
-
(0, child_process_1.spawnSync)('powershell', [
|
|
133
|
-
'-NoProfile',
|
|
134
|
-
'-NonInteractive',
|
|
135
|
-
'-Command',
|
|
136
|
-
`$conns = Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue; foreach ($c in $conns) { taskkill /F /T /PID $c.OwningProcess 2>$null }`,
|
|
137
|
-
], { stdio: 'ignore', timeout: 10000 });
|
|
138
|
-
}
|
|
139
|
-
//# sourceMappingURL=web-server.js.map
|
|
@@ -1 +0,0 @@
|
|
|
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 { spawnSync, 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. Defaults to 'ignore'.\n *\n * Keep the default 'ignore' when calling startWebServer at module load time or from any\n * synchronous context. waitForUrl uses Atomics.wait which blocks the entire Node.js event\n * loop - stdout data handlers cannot fire, so the pipe buffer fills up. On Windows the\n * pipe buffer is only 4 KB: Vite fills it during startup output, blocks on write, and\n * never starts the HTTP server, causing an indefinite hang.\n *\n * Use 'pipe' only when the event loop is free to process I/O during the wait (e.g. when\n * called from an async BeforeAll hook without Atomics.wait).\n */\n stdio?: 'pipe' | 'ignore';\n}\n\nlet serverProcess: ReturnType<typeof spawn> | undefined;\nlet serverPort: number | 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 */\nexport function startWebServer(config: WebServerConfig): void {\n const { command, url, reuseExistingServer = true, timeout = 60000, cwd, env, stdio = 'ignore' } = config;\n\n if (reuseExistingServer && isUrlReachable(url)) {\n console.log(`Reusing existing server at ${url}`);\n return;\n }\n\n serverPort = extractPort(url);\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', stopWebServerOnProcessExit);\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 // 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 const port = serverPort;\n serverProcess = undefined;\n serverPort = undefined;\n if (pid) killProcessTree(pid);\n if (process.platform === 'win32' && port) {\n killProcessOnPort(port);\n }\n}\n\nfunction stopWebServerOnProcessExit(): void {\n if (!serverProcess || serverProcess.killed) return;\n const pid = serverProcess.pid;\n serverProcess = undefined;\n serverPort = undefined;\n if (!pid) return;\n try {\n // Avoid synchronous process-tree cleanup on process exit to prevent rare\n // Windows deadlocks while the Node.js process is shutting down.\n process.kill(pid, 'SIGTERM');\n } catch {\n // ignore - process may have already exited\n }\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 if (process.platform === 'win32') {\n return isUrlReachableOnWindows(url);\n }\n\n // Let the child process exit itself after 2s instead of relying on spawnSync timeout.\n // On Windows this path is avoided because GitLab Windows runners showed rare hangs with\n // node+fetch probes. The PowerShell probe below has a parent timeout as a hard guard.\n const result = spawnSync(\n 'node',\n [\n '-e',\n `setTimeout(()=>process.exit(1),2000);fetch('${url}').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))`,\n ],\n { stdio: 'ignore' }\n );\n return result.status === 0;\n}\n\nfunction isUrlReachableOnWindows(url: string): boolean {\n const escapedUrl = url.replace(/'/g, \"''\");\n const result = spawnSync(\n 'powershell',\n [\n '-NoProfile',\n '-NonInteractive',\n '-Command',\n `$ProgressPreference='SilentlyContinue'; try { $r = Invoke-WebRequest -UseBasicParsing -Uri '${escapedUrl}' -TimeoutSec 2; if ($r.StatusCode -ge 200 -and $r.StatusCode -lt 400) { exit 0 } else { exit 1 } } catch { exit 1 }`,\n ],\n { stdio: 'ignore', timeout: 10000 }\n );\n return result.status === 0;\n}\n\nfunction extractPort(url: string): number | undefined {\n try {\n const parsed = new URL(url);\n const port = parseInt(parsed.port, 10);\n if (port > 0) return port;\n return parsed.protocol === 'https:' ? 443 : 80;\n } catch {\n return undefined;\n }\n}\n\nfunction killProcessOnPort(port: number): void {\n spawnSync(\n 'powershell',\n [\n '-NoProfile',\n '-NonInteractive',\n '-Command',\n `$conns = Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue; foreach ($c in $conns) { taskkill /F /T /PID $c.OwningProcess 2>$null }`,\n ],\n { stdio: 'ignore', timeout: 10000 }\n );\n}\n"]}
|