@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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.5.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Add `smoke-test-one` CLI bin for consumer repos (`smoke-test-one <port>` from app directory).
8
+
3
9
  ## 1.5.0
4
10
 
5
11
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@se-studio/site-check",
3
- "version": "1.5.0",
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);