@se-studio/site-check 1.5.0 → 1.5.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 +5 -3
- package/smoke-test-one.mjs +121 -0
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@se-studio/site-check",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "Validate SE marketing sites (sitemap, llms.txt) and download markdown files preserving structure",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -31,11 +31,13 @@
|
|
|
31
31
|
},
|
|
32
32
|
"bin": {
|
|
33
33
|
"site-check": "./dist/cli.js",
|
|
34
|
-
"site-check-screaming-frog": "./dist/screaming-frog-cli.js"
|
|
34
|
+
"site-check-screaming-frog": "./dist/screaming-frog-cli.js",
|
|
35
|
+
"smoke-test-one": "./smoke-test-one.mjs"
|
|
35
36
|
},
|
|
36
37
|
"files": [
|
|
37
38
|
"dist",
|
|
38
|
-
"*.md"
|
|
39
|
+
"*.md",
|
|
40
|
+
"smoke-test-one.mjs"
|
|
39
41
|
],
|
|
40
42
|
"keywords": [
|
|
41
43
|
"sitemap",
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build, start, and smoke-test one marketing app.
|
|
5
|
+
* Usage: smoke-test-one <port> (from app directory)
|
|
6
|
+
* Skips when SMOKE_TEST_IGNORE=true in .env.local.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
12
|
+
|
|
13
|
+
const port = process.argv[2];
|
|
14
|
+
if (!port || !/^\d+$/.test(port)) {
|
|
15
|
+
console.error('Usage: smoke-test-one <port>');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const appDir = process.cwd();
|
|
20
|
+
const baseUrl = `http://localhost:${port}`;
|
|
21
|
+
const startTimeoutMs = 120_000;
|
|
22
|
+
const pollIntervalMs = 1_000;
|
|
23
|
+
|
|
24
|
+
function parseEnvFile(filePath) {
|
|
25
|
+
const env = {};
|
|
26
|
+
if (!fs.existsSync(filePath)) return env;
|
|
27
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
28
|
+
for (const line of content.split('\n')) {
|
|
29
|
+
const trimmed = line.trim();
|
|
30
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
31
|
+
const eq = trimmed.indexOf('=');
|
|
32
|
+
if (eq === -1) continue;
|
|
33
|
+
const key = trimmed.slice(0, eq).trim();
|
|
34
|
+
let value = trimmed.slice(eq + 1).trim();
|
|
35
|
+
if (value.startsWith('"') && value.endsWith('"')) value = value.slice(1, -1);
|
|
36
|
+
if (value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
37
|
+
env[key] = value;
|
|
38
|
+
}
|
|
39
|
+
return env;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function loadEnv() {
|
|
43
|
+
const envLocal = parseEnvFile(path.join(appDir, '.env.local'));
|
|
44
|
+
const envExample = parseEnvFile(path.join(appDir, '.env.example'));
|
|
45
|
+
return { ...process.env, ...envExample, ...envLocal, PORT: port };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function waitForServer(url) {
|
|
49
|
+
const deadline = Date.now() + startTimeoutMs;
|
|
50
|
+
while (Date.now() < deadline) {
|
|
51
|
+
try {
|
|
52
|
+
const res = await fetch(url, { method: 'GET', redirect: 'follow' });
|
|
53
|
+
if (res.status >= 200 && res.status < 500) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
// Server not ready yet
|
|
58
|
+
}
|
|
59
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
60
|
+
}
|
|
61
|
+
throw new Error(`Server did not respond at ${url} within ${startTimeoutMs}ms`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function killProcessTree(child) {
|
|
65
|
+
if (!child?.pid) return;
|
|
66
|
+
try {
|
|
67
|
+
process.kill(-child.pid, 'SIGTERM');
|
|
68
|
+
} catch {
|
|
69
|
+
try {
|
|
70
|
+
child.kill('SIGTERM');
|
|
71
|
+
} catch {
|
|
72
|
+
// Already exited
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const env = loadEnv();
|
|
78
|
+
|
|
79
|
+
if ((env.SMOKE_TEST_IGNORE ?? '').toLowerCase() === 'true') {
|
|
80
|
+
console.log('Skipped (SMOKE_TEST_IGNORE=true).');
|
|
81
|
+
process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
console.log('Building app...');
|
|
85
|
+
const buildResult = spawnSync('pnpm', ['build'], {
|
|
86
|
+
cwd: appDir,
|
|
87
|
+
stdio: 'inherit',
|
|
88
|
+
env,
|
|
89
|
+
});
|
|
90
|
+
if (buildResult.status !== 0) {
|
|
91
|
+
process.exit(buildResult.status ?? 1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log(`Starting server on ${baseUrl}...`);
|
|
95
|
+
const server = spawn('pnpm', ['start'], {
|
|
96
|
+
cwd: appDir,
|
|
97
|
+
env,
|
|
98
|
+
stdio: 'inherit',
|
|
99
|
+
detached: true,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
let exitCode = 1;
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
await waitForServer(baseUrl);
|
|
106
|
+
console.log('Server ready. Running smoke tests...');
|
|
107
|
+
|
|
108
|
+
const runResult = spawnSync('pnpm', ['smoke-test:run'], {
|
|
109
|
+
cwd: appDir,
|
|
110
|
+
stdio: 'inherit',
|
|
111
|
+
env: { ...env, SMOKE_TEST_PORT: port },
|
|
112
|
+
});
|
|
113
|
+
exitCode = runResult.status ?? 1;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error(error instanceof Error ? error.message : error);
|
|
116
|
+
exitCode = 1;
|
|
117
|
+
} finally {
|
|
118
|
+
killProcessTree(server);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
process.exit(exitCode);
|