@se-studio/site-check 2.1.0 → 2.1.1
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/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/smoke-test-one.mjs +122 -55
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fix `smoke-test-one` appearing to hang: add fetch timeouts, startup/progress logging, fail fast when port is busy but not responding, reuse an already-healthy dev server (`SMOKE_TEST_USE_RUNNING_SERVER`, default true), and inherit stdio so dev server output is visible.
|
|
8
|
+
|
|
3
9
|
## 2.1.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/package.json
CHANGED
package/smoke-test-one.mjs
CHANGED
|
@@ -5,13 +5,15 @@
|
|
|
5
5
|
* Usage: smoke-test-one <port> (from app directory)
|
|
6
6
|
*
|
|
7
7
|
* Env:
|
|
8
|
-
* SMOKE_TEST_IGNORE=true
|
|
9
|
-
* SMOKE_TEST_SERVER_SCRIPT=dev:dev
|
|
10
|
-
* SMOKE_TEST_VERIFY_CACHE=true
|
|
11
|
-
* SMOKE_TEST_READY_PATH=/
|
|
12
|
-
*
|
|
8
|
+
* SMOKE_TEST_IGNORE=true — skip entirely
|
|
9
|
+
* SMOKE_TEST_SERVER_SCRIPT=dev:dev — pnpm script to start server (default dev:dev; use "start" for prod)
|
|
10
|
+
* SMOKE_TEST_VERIFY_CACHE=true — run smoke twice and assert cache headers / timing
|
|
11
|
+
* SMOKE_TEST_READY_PATH=/ — path polled until server responds
|
|
12
|
+
* SMOKE_TEST_USE_RUNNING_SERVER=true — reuse server already on port (default true)
|
|
13
|
+
* LOG_CMS_FETCH=1 — set automatically when SMOKE_TEST_VERIFY_CACHE=true
|
|
13
14
|
*/
|
|
14
15
|
|
|
16
|
+
import net from 'node:net';
|
|
15
17
|
import fs from 'node:fs';
|
|
16
18
|
import path from 'node:path';
|
|
17
19
|
import { spawn, spawnSync } from 'node:child_process';
|
|
@@ -25,6 +27,15 @@ if (!port || !/^\d+$/.test(port)) {
|
|
|
25
27
|
const appDir = process.cwd();
|
|
26
28
|
const baseUrl = `http://localhost:${port}`;
|
|
27
29
|
const pollIntervalMs = 1_000;
|
|
30
|
+
const progressIntervalMs = 10_000;
|
|
31
|
+
const fetchTimeoutMs = 10_000;
|
|
32
|
+
|
|
33
|
+
function fetchWithTimeout(url, options = {}) {
|
|
34
|
+
return fetch(url, {
|
|
35
|
+
...options,
|
|
36
|
+
signal: AbortSignal.timeout(fetchTimeoutMs),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
28
39
|
|
|
29
40
|
function parseEnvFile(filePath) {
|
|
30
41
|
const env = {};
|
|
@@ -49,20 +60,70 @@ function loadEnv() {
|
|
|
49
60
|
return { ...process.env, ...envLocal, PORT: port };
|
|
50
61
|
}
|
|
51
62
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
function sleep(ms) {
|
|
64
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function isServerUp(url) {
|
|
68
|
+
try {
|
|
69
|
+
const res = await fetchWithTimeout(url, { method: 'GET', redirect: 'follow' });
|
|
70
|
+
return res.status >= 200 && res.status < 500;
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function isPortListening(listenPort) {
|
|
77
|
+
return new Promise((resolve) => {
|
|
78
|
+
const socket = net.connect(Number(listenPort), '127.0.0.1');
|
|
79
|
+
socket.setTimeout(2_000);
|
|
80
|
+
socket.once('connect', () => {
|
|
81
|
+
socket.destroy();
|
|
82
|
+
resolve(true);
|
|
83
|
+
});
|
|
84
|
+
socket.once('timeout', () => {
|
|
85
|
+
socket.destroy();
|
|
86
|
+
resolve(false);
|
|
87
|
+
});
|
|
88
|
+
socket.once('error', () => resolve(false));
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function waitForServer(url, timeoutMs, options = {}) {
|
|
93
|
+
const { child, label = 'Server' } = options;
|
|
94
|
+
const started = Date.now();
|
|
95
|
+
let lastProgressAt = 0;
|
|
96
|
+
let childExited = false;
|
|
97
|
+
let childExitCode = null;
|
|
98
|
+
|
|
99
|
+
if (child) {
|
|
100
|
+
child.on('exit', (code) => {
|
|
101
|
+
childExited = true;
|
|
102
|
+
childExitCode = code;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
while (Date.now() - started < timeoutMs) {
|
|
107
|
+
if (childExited) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`${label} process exited with code ${childExitCode ?? 'unknown'} before ${url} was ready`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (await isServerUp(url)) {
|
|
114
|
+
return Date.now() - started;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const elapsed = Date.now() - started;
|
|
118
|
+
if (elapsed - lastProgressAt >= progressIntervalMs) {
|
|
119
|
+
console.log(`Still waiting for ${url} (${Math.round(elapsed / 1000)}s)...`);
|
|
120
|
+
lastProgressAt = elapsed;
|
|
62
121
|
}
|
|
63
|
-
|
|
122
|
+
|
|
123
|
+
await sleep(pollIntervalMs);
|
|
64
124
|
}
|
|
65
|
-
|
|
125
|
+
|
|
126
|
+
throw new Error(`${label} did not respond at ${url} within ${timeoutMs}ms`);
|
|
66
127
|
}
|
|
67
128
|
|
|
68
129
|
function killProcessTree(child) {
|
|
@@ -78,21 +139,6 @@ function killProcessTree(child) {
|
|
|
78
139
|
}
|
|
79
140
|
}
|
|
80
141
|
|
|
81
|
-
function createLogForwarder(onLine) {
|
|
82
|
-
let buffer = '';
|
|
83
|
-
return (chunk) => {
|
|
84
|
-
process.stdout.write(chunk);
|
|
85
|
-
buffer += chunk.toString();
|
|
86
|
-
let newlineIndex = buffer.indexOf('\n');
|
|
87
|
-
while (newlineIndex !== -1) {
|
|
88
|
-
const line = buffer.slice(0, newlineIndex);
|
|
89
|
-
buffer = buffer.slice(newlineIndex + 1);
|
|
90
|
-
onLine(line);
|
|
91
|
-
newlineIndex = buffer.indexOf('\n');
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
142
|
const env = loadEnv();
|
|
97
143
|
|
|
98
144
|
if ((env.SMOKE_TEST_IGNORE ?? '').toLowerCase() === 'true') {
|
|
@@ -102,6 +148,7 @@ if ((env.SMOKE_TEST_IGNORE ?? '').toLowerCase() === 'true') {
|
|
|
102
148
|
|
|
103
149
|
const serverScript = env.SMOKE_TEST_SERVER_SCRIPT ?? 'dev:dev';
|
|
104
150
|
const verifyCache = (env.SMOKE_TEST_VERIFY_CACHE ?? '').toLowerCase() === 'true';
|
|
151
|
+
const useRunningServer = (env.SMOKE_TEST_USE_RUNNING_SERVER ?? 'true').toLowerCase() !== 'false';
|
|
105
152
|
const readyPath = env.SMOKE_TEST_READY_PATH ?? '/';
|
|
106
153
|
const readyUrl = `${baseUrl}${readyPath.startsWith('/') ? readyPath : `/${readyPath}`}`;
|
|
107
154
|
const isProductionStart = serverScript === 'start';
|
|
@@ -119,36 +166,52 @@ if (isProductionStart) {
|
|
|
119
166
|
}
|
|
120
167
|
}
|
|
121
168
|
|
|
122
|
-
let cmsFetchCount = 0;
|
|
123
|
-
|
|
124
|
-
const logForwarder = createLogForwarder((line) => {
|
|
125
|
-
if (!line.includes('[CMS fetch]')) return;
|
|
126
|
-
cmsFetchCount++;
|
|
127
|
-
});
|
|
128
|
-
|
|
129
169
|
const smokeEnv = {
|
|
130
170
|
...env,
|
|
131
171
|
SMOKE_TEST_PORT: port,
|
|
132
172
|
...(verifyCache ? { SMOKE_TEST_VERIFY_CACHE: 'true', LOG_CMS_FETCH: '1' } : {}),
|
|
133
173
|
};
|
|
134
174
|
|
|
135
|
-
console.log(`
|
|
136
|
-
const server = spawn('pnpm', [serverScript], {
|
|
137
|
-
cwd: appDir,
|
|
138
|
-
env: smokeEnv,
|
|
139
|
-
stdio: ['inherit', 'pipe', 'pipe'],
|
|
140
|
-
detached: true,
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
server.stdout?.on('data', logForwarder);
|
|
144
|
-
server.stderr?.on('data', logForwarder);
|
|
175
|
+
console.log(`Smoke test: checking ${readyUrl} (port ${port})...`);
|
|
145
176
|
|
|
177
|
+
let server = null;
|
|
178
|
+
let spawnedServer = false;
|
|
146
179
|
let exitCode = 1;
|
|
147
180
|
|
|
148
181
|
try {
|
|
149
|
-
await
|
|
150
|
-
|
|
182
|
+
const serverUp = await isServerUp(readyUrl);
|
|
183
|
+
const portBusy = await isPortListening(port);
|
|
184
|
+
|
|
185
|
+
if (serverUp && useRunningServer) {
|
|
186
|
+
console.log(`Using existing server at ${readyUrl}.`);
|
|
187
|
+
} else if (serverUp && !useRunningServer) {
|
|
188
|
+
console.error(
|
|
189
|
+
`Server already running at ${readyUrl}. Stop it first, or set SMOKE_TEST_USE_RUNNING_SERVER=true.`,
|
|
190
|
+
);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
} else if (portBusy) {
|
|
193
|
+
console.error(
|
|
194
|
+
`Port ${port} is in use but ${readyUrl} is not responding. Stop the process on :${port} and retry.`,
|
|
195
|
+
);
|
|
196
|
+
process.exit(1);
|
|
197
|
+
} else {
|
|
198
|
+
console.log(`Starting server (${serverScript}) on ${baseUrl}...`);
|
|
199
|
+
server = spawn('pnpm', [serverScript], {
|
|
200
|
+
cwd: appDir,
|
|
201
|
+
env: smokeEnv,
|
|
202
|
+
stdio: 'inherit',
|
|
203
|
+
detached: true,
|
|
204
|
+
});
|
|
205
|
+
spawnedServer = true;
|
|
206
|
+
|
|
207
|
+
const readyMs = await waitForServer(readyUrl, startTimeoutMs, {
|
|
208
|
+
child: server,
|
|
209
|
+
label: 'Dev server',
|
|
210
|
+
});
|
|
211
|
+
console.log(`Server ready at ${readyUrl} (${Math.round(readyMs / 1000)}s).`);
|
|
212
|
+
}
|
|
151
213
|
|
|
214
|
+
console.log('Running smoke tests...');
|
|
152
215
|
const runResult = spawnSync('pnpm', ['smoke-test:run'], {
|
|
153
216
|
cwd: appDir,
|
|
154
217
|
stdio: 'inherit',
|
|
@@ -158,15 +221,19 @@ try {
|
|
|
158
221
|
|
|
159
222
|
if (verifyCache) {
|
|
160
223
|
console.log('');
|
|
161
|
-
console.log('## CMS fetch
|
|
162
|
-
console.log(
|
|
163
|
-
|
|
224
|
+
console.log('## CMS fetch logging');
|
|
225
|
+
console.log(
|
|
226
|
+
' LOG_CMS_FETCH=1 is set on the dev server — check its terminal for [CMS fetch] lines.',
|
|
227
|
+
);
|
|
228
|
+
console.log(' Pass-level cache stats are in the smoke report (x-nextjs-cache).');
|
|
164
229
|
}
|
|
165
230
|
} catch (error) {
|
|
166
231
|
console.error(error instanceof Error ? error.message : error);
|
|
167
232
|
exitCode = 1;
|
|
168
233
|
} finally {
|
|
169
|
-
|
|
234
|
+
if (spawnedServer) {
|
|
235
|
+
killProcessTree(server);
|
|
236
|
+
}
|
|
170
237
|
}
|
|
171
238
|
|
|
172
239
|
process.exit(exitCode);
|