plum-e2e 2.6.6 → 2.6.8
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/backend/config/scripts/generate-report.js +8 -0
- package/backend/lib/runnerProcess.js +32 -13
- package/backend/logs/runner-cmrkl8sym0000qy01euz4270n.log +18 -0
- package/backend/logs/runner-cmrkl9oyx0001qy0137nzr94d.log +18 -0
- package/backend/scripts/manage-runners.mjs +6 -1
- package/backend/server.js +19 -5
- package/package.json +1 -1
|
@@ -32,6 +32,14 @@ if (!fs.existsSync(jsonReportFile)) {
|
|
|
32
32
|
// and saves the combined report after all lanes finish.
|
|
33
33
|
if (process.env.PLUM_MODE === 'node') process.exit(0);
|
|
34
34
|
|
|
35
|
+
// `run-test` runs on the host without Docker/Postgres, so DATABASE_URL is
|
|
36
|
+
// never set there — skip the DB save instead of letting Prisma's connection
|
|
37
|
+
// attempt surface as an unhandled-looking stack trace after tests already ran.
|
|
38
|
+
if (!process.env.DATABASE_URL) {
|
|
39
|
+
console.log('No DATABASE_URL configured — skipping report save to database.');
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
|
|
35
43
|
(async () => {
|
|
36
44
|
try {
|
|
37
45
|
const raw = JSON.parse(fs.readFileSync(jsonReportFile, 'utf8'));
|
|
@@ -77,12 +77,17 @@ function findPidOnPort(port) {
|
|
|
77
77
|
if (process.platform === 'win32') {
|
|
78
78
|
const out = execSync('netstat -ano', { encoding: 'utf8' });
|
|
79
79
|
for (const line of out.split('\n')) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
if (!line.toUpperCase().includes('LISTENING')) continue;
|
|
81
|
+
const parts = line.trim().split(/\s+/);
|
|
82
|
+
// Columns: Proto, Local Address, Foreign Address, State, PID.
|
|
83
|
+
// Match the port exactly off the local address (e.g. "0.0.0.0:3002"
|
|
84
|
+
// or "[::]:3002") — a substring check would false-positive port
|
|
85
|
+
// "300" against "3002", "13000", etc.
|
|
86
|
+
const localAddress = parts[1] ?? '';
|
|
87
|
+
const localPort = localAddress.slice(localAddress.lastIndexOf(':') + 1);
|
|
88
|
+
if (localPort !== portStr) continue;
|
|
89
|
+
const pid = parseInt(parts[parts.length - 1], 10);
|
|
90
|
+
if (!isNaN(pid) && pid > 0) return pid;
|
|
86
91
|
}
|
|
87
92
|
} else {
|
|
88
93
|
const out = execSync(`lsof -i :${portStr} -t -sTCP:LISTEN`, { encoding: 'utf8' }).trim();
|
|
@@ -122,12 +127,18 @@ function parsePort(url) {
|
|
|
122
127
|
}
|
|
123
128
|
}
|
|
124
129
|
|
|
125
|
-
/**
|
|
130
|
+
/**
|
|
131
|
+
* Clears the pid of registry entries whose process has died, and persists the
|
|
132
|
+
* result. Keeps the entry itself (with its `port`) rather than deleting it —
|
|
133
|
+
* that's the only place the last-used port for a runner is remembered, so a
|
|
134
|
+
* later Start can reuse it instead of falling back to whatever's in the
|
|
135
|
+
* runner's URL (or a hardcoded default, if the URL has no explicit port).
|
|
136
|
+
*/
|
|
126
137
|
function pruneDead(registry = loadRegistry()) {
|
|
127
138
|
let changed = false;
|
|
128
139
|
for (const [id, entry] of Object.entries(registry)) {
|
|
129
|
-
if (
|
|
130
|
-
|
|
140
|
+
if (entry?.pid && !isAlive(entry.pid)) {
|
|
141
|
+
registry[id] = { ...entry, pid: null };
|
|
131
142
|
changed = true;
|
|
132
143
|
}
|
|
133
144
|
}
|
|
@@ -233,10 +244,10 @@ function stopNode(id, fallbackPort = null) {
|
|
|
233
244
|
let signalled = false;
|
|
234
245
|
|
|
235
246
|
let pid = entry?.pid && isAlive(entry.pid) ? entry.pid : null;
|
|
247
|
+
const port = fallbackPort ?? (entry?.port ? Number(entry.port) : null);
|
|
236
248
|
|
|
237
|
-
if (!pid) {
|
|
238
|
-
|
|
239
|
-
if (port) pid = findPidOnPort(port);
|
|
249
|
+
if (!pid && port) {
|
|
250
|
+
pid = findPidOnPort(port);
|
|
240
251
|
}
|
|
241
252
|
|
|
242
253
|
if (pid) {
|
|
@@ -245,7 +256,15 @@ function stopNode(id, fallbackPort = null) {
|
|
|
245
256
|
signalled = true;
|
|
246
257
|
} catch {}
|
|
247
258
|
}
|
|
248
|
-
|
|
259
|
+
|
|
260
|
+
// Keep the port on record (clearing only the pid) so a later Start reuses
|
|
261
|
+
// the same port instead of falling back to the runner's URL — which may
|
|
262
|
+
// not even have an explicit port — or a hardcoded default.
|
|
263
|
+
if (port) {
|
|
264
|
+
registry[id] = { ...entry, pid: null, port: String(port) };
|
|
265
|
+
} else {
|
|
266
|
+
delete registry[id];
|
|
267
|
+
}
|
|
249
268
|
saveRegistry(registry);
|
|
250
269
|
return signalled;
|
|
251
270
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
This file is part of Plum.
|
|
3
|
+
|
|
4
|
+
Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
it under the terms of the GNU General Public License as published by
|
|
6
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
(at your option) any later version.
|
|
8
|
+
|
|
9
|
+
Plum is distributed in the hope that it will be useful,
|
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
GNU General Public License for more details.
|
|
13
|
+
|
|
14
|
+
You should have received a copy of the GNU General Public License
|
|
15
|
+
along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
*/
|
|
17
|
+
📂 Loading tests from: /Users/silverlunah/Projects/plum/backend/tests
|
|
18
|
+
Backend running on port 3996 (node/runner mode)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
This file is part of Plum.
|
|
3
|
+
|
|
4
|
+
Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
it under the terms of the GNU General Public License as published by
|
|
6
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
(at your option) any later version.
|
|
8
|
+
|
|
9
|
+
Plum is distributed in the hope that it will be useful,
|
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
GNU General Public License for more details.
|
|
13
|
+
|
|
14
|
+
You should have received a copy of the GNU General Public License
|
|
15
|
+
along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
*/
|
|
17
|
+
📂 Loading tests from: /Users/silverlunah/Projects/plum/backend/tests
|
|
18
|
+
Backend running on port 3996 (node/runner mode)
|
|
@@ -204,7 +204,12 @@ async function runAction(r) {
|
|
|
204
204
|
const action = await clack.select({ message: `${r.name} — ${r.url}`, options });
|
|
205
205
|
if (cancelled(action) || action === 'back') return;
|
|
206
206
|
|
|
207
|
-
|
|
207
|
+
// Prefer the port this runner actually last ran on (remembered in the
|
|
208
|
+
// local registry even after being stopped) over parsing the URL — the URL
|
|
209
|
+
// may not carry an explicit port at all, and would otherwise silently fall
|
|
210
|
+
// back to the primary's own default port.
|
|
211
|
+
const remembered = runnerProcess.loadRegistry()[r.id]?.port;
|
|
212
|
+
const port = remembered || parsePort(r.url);
|
|
208
213
|
|
|
209
214
|
if (action === 'start') {
|
|
210
215
|
const s = clack.spinner();
|
package/backend/server.js
CHANGED
|
@@ -92,16 +92,30 @@ async function start() {
|
|
|
92
92
|
try {
|
|
93
93
|
const reg = loadRegistry();
|
|
94
94
|
// A self-restart already wrote the replacement's pid under this
|
|
95
|
-
// id before this process exits — only
|
|
96
|
-
// still ours, so we don't
|
|
95
|
+
// id before this process exits — only touch the entry if it's
|
|
96
|
+
// still ours, so we don't clobber the new process's registration.
|
|
97
|
+
// Keep the entry (with its port) rather than deleting it — that's
|
|
98
|
+
// the only place a later manual Start can find the port this
|
|
99
|
+
// runner was last running on.
|
|
97
100
|
if (reg[runnerId]?.pid === process.pid) {
|
|
98
|
-
|
|
101
|
+
reg[runnerId] = { ...reg[runnerId], pid: null };
|
|
99
102
|
saveRegistry(reg);
|
|
100
103
|
}
|
|
101
104
|
} catch {}
|
|
102
105
|
};
|
|
103
|
-
|
|
104
|
-
|
|
106
|
+
// Adding a SIGTERM/SIGINT listener suppresses Node's default
|
|
107
|
+
// "terminate immediately" behavior — the handler must exit itself,
|
|
108
|
+
// or the process (and the port it's bound to) lives on forever
|
|
109
|
+
// after a plain `kill`/SIGTERM with nothing left to stop it short
|
|
110
|
+
// of SIGKILL.
|
|
111
|
+
process.once('SIGTERM', () => {
|
|
112
|
+
cleanup();
|
|
113
|
+
process.exit(0);
|
|
114
|
+
});
|
|
115
|
+
process.once('SIGINT', () => {
|
|
116
|
+
cleanup();
|
|
117
|
+
process.exit(0);
|
|
118
|
+
});
|
|
105
119
|
process.once('exit', cleanup);
|
|
106
120
|
}
|
|
107
121
|
return;
|