@gnuechtel/nx-cucumber 24.3.1 → 24.3.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/README.md CHANGED
@@ -172,6 +172,11 @@ affected until a repaired Nx patch is adopted. See
172
172
 
173
173
  This plugin requires [Node.js](https://nodejs.org/), [Nx](https://nx.dev/) and [Cucumber](https://github.com/cucumber/cucumber-js). Version 24.3.0 targets Nx `>=24 <25` and Node.js 24 or 26; published 2.x packages remain the Nx-22 path. Plugin 23.x remains available for Nx 23. Browser automation uses the [Playwright library](https://playwright.dev/docs/library) when selected by the generator. Consult `package.json` for all dependency requirements and `.gitlab-ci.yml` for the pinned CI versions. Minor and patch versions follow this plugin's changes independently of Nx.
174
174
 
175
+ For generated Cucumber/Playwright runners on Linux and macOS, reclaiming an
176
+ occupied server port requires `lsof` in `PATH` (for example,
177
+ `apt-get install lsof` on Debian-based Linux). An unused port needs no additional
178
+ utility. Windows uses `netstat` and `taskkill`.
179
+
175
180
  ## Quick start
176
181
 
177
182
  To immediately use this plugin with a new example React application use the following commands:
@@ -277,7 +282,7 @@ The `e2e` target behavior depends on the selected test runner:
277
282
  - **Core runner:** no explicit `e2e` target is generated. The `e2e` task is inferred by the plugin from `cucumber.js`.
278
283
  - **Playwright runner:** explicit command targets are generated:
279
284
  - `e2e-prepare` installs Playwright Chromium (Linux uses `--with-deps`) and is configured with `cache: false`.
280
- - `e2e` runs a generated `run-e2e.js` lifecycle script that manages the full dev server lifecycle (clear port, start server, wait for readiness, run Cucumber, stop server) using only Node.js built-ins.
285
+ - `e2e` runs a generated `run-e2e.js` lifecycle script that manages the full dev server lifecycle (clear port, start server, wait for readiness, run Cucumber, stop server) using Node.js built-ins and the Unix `lsof` utility for reclaiming occupied ports.
281
286
  - `e2e-ci` runs `e2e-prepare` and then `e2e`.
282
287
 
283
288
  ### Additional project generator options
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gnuechtel/nx-cucumber",
3
- "version": "24.3.1",
3
+ "version": "24.3.2",
4
4
  "description": "Plugin to use Cucumber within Nx workspaces",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -55,15 +55,20 @@ async function run() {
55
55
  stdio: 'pipe',
56
56
  });
57
57
  } else {
58
- // Let Nx terminate its own detached task processes before forcing exit.
58
+ // The shell can close before its descendants. Give the whole process
59
+ // group a grace period, then stop any remaining server processes.
59
60
  process.kill(-server.pid, 'SIGTERM');
60
- if (!processClosed) {
61
- await Promise.race([
62
- new Promise((resolve) => server.once('close', resolve)),
63
- sleep(5_000),
64
- ]);
61
+ const deadline = Date.now() + 5_000;
62
+ while (Date.now() < deadline) {
63
+ try {
64
+ process.kill(-server.pid, 0);
65
+ } catch (error) {
66
+ if (error.code === 'ESRCH') return;
67
+ throw error;
68
+ }
69
+ await sleep(50);
65
70
  }
66
- if (!processClosed) process.kill(-server.pid, 'SIGKILL');
71
+ process.kill(-server.pid, 'SIGKILL');
67
72
  }
68
73
  } catch {
69
74
  /* process already exited */
@@ -74,16 +79,12 @@ async function run() {
74
79
  const deadline = Date.now() + SERVER_START_TIMEOUT_MS;
75
80
  while (!(await portOpen(PORT))) {
76
81
  if (processClosed) {
77
- console.error(
78
- '[nx-cucumber] Server process exited before becoming ready',
79
- );
82
+ console.error('[nx-cucumber] Server process exited before becoming ready');
80
83
  process.exit(1);
81
84
  }
82
85
  if (Date.now() > deadline) {
83
86
  await teardown();
84
- console.error(
85
- `[nx-cucumber] Server did not respond on port ${PORT} within ${SERVER_START_TIMEOUT_MS / 1000}s`,
86
- );
87
+ console.error(`[nx-cucumber] Server did not respond on port ${PORT} within ${SERVER_START_TIMEOUT_MS / 1000}s`);
87
88
  process.exit(1);
88
89
  }
89
90
  await sleep(500);
@@ -130,13 +131,29 @@ async function clearPort() {
130
131
  /* port already free */
131
132
  }
132
133
  } else {
134
+ let listenerPids = '';
133
135
  try {
134
- cp.execSync(`lsof -ti :${PORT} | xargs kill -9`, {
135
- shell: true,
136
- stdio: 'pipe',
136
+ listenerPids = cp.execFileSync('lsof', ['-nP', '-t', `-iTCP:${PORT}`, '-sTCP:LISTEN'], {
137
+ encoding: 'utf8',
138
+ stdio: ['ignore', 'pipe', 'pipe'],
137
139
  });
138
- } catch {
139
- /* port already free */
140
+ } catch (error) {
141
+ if (error.code === 'ENOENT') {
142
+ throw new Error(
143
+ `[nx-cucumber] lsof is required to reclaim port ${PORT}. Install lsof or free the port manually.`,
144
+ );
145
+ }
146
+ // Exit 1 with no output means the listener disappeared after the probe.
147
+ if (error.status !== 1 || error.stdout?.length || error.stderr?.length) {
148
+ throw new Error(`[nx-cucumber] Failed to inspect port ${PORT} with lsof (${error.code || error.status}).`);
149
+ }
150
+ }
151
+ for (const pid of new Set(listenerPids.trim().split(/\s+/).filter(Boolean))) {
152
+ try {
153
+ process.kill(Number(pid), 'SIGKILL');
154
+ } catch (error) {
155
+ if (error.code !== 'ESRCH') throw error;
156
+ }
140
157
  }
141
158
  }
142
159