@gnuechtel/nx-cucumber 23.1.1-17636405033 → 23.1.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
@@ -73,8 +73,8 @@ Previously published shared packages stay available for older plugin versions
73
73
  and direct consumers; no replacement public helper APIs are introduced.
74
74
  Standalone-package deprecation is a separate later operation.
75
75
 
76
- See the [23.1.0 release notes](../../docs/release-notes/23.1.0.md). Publication is
77
- not triggered by this preparation; use the protected release-branch workflow.
76
+ See the [23.1.0 release notes](../../docs/release-notes/23.1.0.md). Further
77
+ maintenance releases use the protected [support-branch workflow](../../docs/maintenance-releases.md).
78
78
 
79
79
  ## Compatibility and versioning
80
80
 
@@ -136,6 +136,11 @@ Version 2.x is a deliberate breaking release that simplifies the plugin architec
136
136
 
137
137
  This plugin requires [Node.js](https://nodejs.org/), [Nx](https://nx.dev/) and [Cucumber](https://github.com/cucumber/cucumber-js). Version 23.1.0 targets Nx `>=23 <24` and Node.js 24 or 26; published 2.x packages remain the Nx-22 path. The jump follows the supported Nx major and does not promise future major releases. 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.
138
138
 
139
+ For generated Cucumber/Playwright runners on Linux and macOS, reclaiming an
140
+ occupied server port requires `lsof` in `PATH` (for example,
141
+ `apt-get install lsof` on Debian-based Linux). An unused port needs no additional
142
+ utility. Windows uses `netstat` and `taskkill`.
143
+
139
144
  ## Quick start
140
145
 
141
146
  To immediately use this plugin with a new example React application use the following commands:
@@ -244,7 +249,7 @@ The `e2e` target behavior depends on the selected test runner:
244
249
  - **Core runner:** no explicit `e2e` target is generated. The `e2e` task is inferred by the plugin from `cucumber.js`.
245
250
  - **Playwright runner:** explicit command targets are generated:
246
251
  - `e2e-prepare` installs Playwright Chromium (Linux uses `--with-deps`) and is configured with `cache: false`.
247
- - `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.
252
+ - `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.
248
253
  - `e2e-ci` runs `e2e-prepare` and then `e2e`.
249
254
 
250
255
  ### Additional project generator options
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gnuechtel/nx-cucumber",
3
- "version": "23.1.1-17636405033",
3
+ "version": "23.1.2",
4
4
  "description": "Plugin to use Cucumber within Nx workspaces",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -47,4 +47,4 @@
47
47
  "optional": true
48
48
  }
49
49
  }
50
- }
50
+ }
@@ -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