Files
speedboard/runner.js
Malin 59c2403f43 fix: replace broken symlink with REPORTS_DIR env var
The ln -sf /data/reports /app/reports was creating the symlink INSIDE
/app/reports/ (since that dir already existed from COPY) instead of
replacing it. Result: sitespeed.io wrote to /app/reports/<id> and the
parser looked there too, but the volume was at /data/reports.

Fix: set REPORTS_DIR=/data/reports in Docker ENV and use it in both
runner.js (outputFolder) and app.js (static serving). No symlink needed.
Also add .dockerignore to exclude reports/, node_modules/, .git/.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 10:49:20 +02:00

89 lines
2.9 KiB
JavaScript

import { spawn } from 'child_process';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { existsSync } from 'fs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const LOCAL_BIN = join(__dirname, '..', 'sitespeed.io', 'bin', 'sitespeed.js');
const REPORTS_DIR = process.env.REPORTS_DIR || join(__dirname, 'reports');
export function runTest(job, onLine) {
return new Promise((resolve, reject) => {
const outputFolder = join(REPORTS_DIR, job.id);
const isDocker = !!process.env.IN_DOCKER;
const sitespeedArgs = [
job.url,
'--browser', job.browser,
'-n', String(job.runs),
'--outputFolder', outputFolder,
'--json',
'--sustainable.enable',
'--sustainable.useGreenWebHostingAPI',
'--axe.enable',
'--coach',
];
if (job.mobile) sitespeedArgs.push('--mobile');
if (isDocker) {
sitespeedArgs.push('--browsertime.chrome.args', 'no-sandbox');
sitespeedArgs.push('--browsertime.chrome.args', 'disable-dev-shm-usage');
sitespeedArgs.push('--browsertime.chrome.args', 'disable-gpu');
}
// Do not force DISPLAY — sitespeed.io starts and manages its own Xvfb
const env = { ...process.env };
let child;
if (isDocker) {
// SITESPEED_BIN is set by start.sh from the build-time path discovery
const bin = process.env.SITESPEED_BIN;
if (!bin) {
return reject(new Error(
'SITESPEED_BIN is not set. The Docker build may not have found sitespeed.js.\n' +
'Check build logs for "Build-time sitespeed.js found at:"'
));
}
onLine(`[runner] node ${bin}`);
onLine(`[runner] DISPLAY=${env.DISPLAY}`);
child = spawn('node', [bin, ...sitespeedArgs], { cwd: __dirname, env });
} else {
if (!existsSync(LOCAL_BIN)) {
return reject(new Error(
`Local sitespeed.io not found at ${LOCAL_BIN}\n` +
`Run: cd /home/malin/c0ding/sitespeed.io && npm install`
));
}
onLine(`[runner] node ${LOCAL_BIN.slice(-40)}...`);
child = spawn('node', [LOCAL_BIN, ...sitespeedArgs], { cwd: __dirname, env });
}
const allLines = [];
child.stdout.on('data', (data) => {
const lines = data.toString().split('\n').filter(Boolean);
for (const line of lines) { allLines.push(line); onLine(line); }
});
child.stderr.on('data', (data) => {
const lines = data.toString().split('\n').filter(Boolean);
for (const line of lines) { allLines.push('[stderr] ' + line); onLine('[stderr] ' + line); }
});
child.on('close', (code) => {
if (code === 0) {
resolve(outputFolder);
} else {
const tail = allLines.slice(-20).join('\n');
reject(new Error(`sitespeed.io exited with code ${code}\n${tail}`));
}
});
child.on('error', (err) => {
reject(new Error(`Failed to spawn process: ${err.message}`));
});
});
}