89 lines
3.5 KiB
JavaScript
89 lines
3.5 KiB
JavaScript
import { execFile } from 'node:child_process';
|
|
import { access, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { promisify } from 'node:util';
|
|
import toolInvocation from '../src/shared/tool-invocation.cjs';
|
|
|
|
const exec = promisify(execFile);
|
|
const { npmProbeCandidates } = toolInvocation;
|
|
const packageJson = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
|
|
const checks = [];
|
|
const jsonMode = process.argv.includes('--json');
|
|
|
|
function add(id, name, ok, detail, help = '') {
|
|
checks.push({ id, name, status: ok ? 'pass' : 'fail', ok, detail, help });
|
|
}
|
|
|
|
const major = Number(process.versions.node.split('.')[0]);
|
|
add('node', 'Node.js', major >= 22, process.version, 'Install Node.js 22 or newer.');
|
|
|
|
try {
|
|
const failures = [];
|
|
let version = '';
|
|
let source = '';
|
|
for (const candidate of npmProbeCandidates()) {
|
|
try {
|
|
const { stdout } = await exec(candidate.file, candidate.args, { windowsHide: true });
|
|
version = stdout.trim();
|
|
source = candidate.source;
|
|
if (version) break;
|
|
} catch (error) {
|
|
failures.push(`${candidate.source}: ${error.message}`);
|
|
}
|
|
}
|
|
if (!version) throw new Error(failures.join(' | ') || 'No npm invocation candidate succeeded.');
|
|
add('npm', 'npm', true, `${version} (${source})`);
|
|
} catch (error) {
|
|
add('npm', 'npm', false, error.message, 'Install npm together with Node.js and ensure npm.cmd is available on PATH.');
|
|
}
|
|
|
|
try {
|
|
const { stdout } = await exec('git', ['--version']);
|
|
add('git', 'Git', true, stdout.trim());
|
|
const [name, email] = await Promise.all([
|
|
exec('git', ['config', '--global', '--get', 'user.name']).then((result) => result.stdout.trim()).catch(() => ''),
|
|
exec('git', ['config', '--global', '--get', 'user.email']).then((result) => result.stdout.trim()).catch(() => '')
|
|
]);
|
|
add('git-identity', 'Git identity', Boolean(name && email), name && email ? `${name} <${email}>` : 'user.name or user.email is missing', 'Configure git config --global user.name and user.email.');
|
|
} catch (error) {
|
|
add('git', 'Git', false, error.message, 'Install Git and ensure git is on PATH.');
|
|
}
|
|
|
|
try {
|
|
await access(new URL('../node_modules/electron/package.json', import.meta.url));
|
|
add('electron', 'Electron dependency', true, 'installed');
|
|
} catch {
|
|
add('electron', 'Electron dependency', false, 'not installed', 'Run npm install.');
|
|
}
|
|
|
|
let markerDirectory = null;
|
|
try {
|
|
markerDirectory = await mkdtemp(path.join(os.tmpdir(), 'forgeflow-doctor-'));
|
|
await writeFile(path.join(markerDirectory, 'write-test'), 'ok');
|
|
add('temp-storage', 'Local diagnostic storage', true, markerDirectory.replace(os.homedir(), '<HOME>'));
|
|
} catch (error) {
|
|
add('temp-storage', 'Local diagnostic storage', false, error.message, 'Check local disk permissions and free space.');
|
|
} finally {
|
|
if (markerDirectory) await rm(markerDirectory, { recursive: true, force: true }).catch(() => {});
|
|
}
|
|
|
|
const report = {
|
|
product: 'ForgeFlow',
|
|
version: packageJson.version,
|
|
generatedAt: new Date().toISOString(),
|
|
platform: process.platform,
|
|
arch: process.arch,
|
|
ready: checks.every((check) => check.ok),
|
|
checks
|
|
};
|
|
|
|
if (jsonMode) console.log(JSON.stringify(report, null, 2));
|
|
else {
|
|
console.log('ForgeFlow doctor\n');
|
|
for (const check of checks) console.log(`${check.ok ? 'PASS' : 'FAIL'} ${check.name.padEnd(26)} ${check.detail}`);
|
|
console.log(`\n${report.ready ? 'Environment is ready.' : 'Resolve failed checks before starting ForgeFlow.'}`);
|
|
}
|
|
|
|
if (!report.ready) process.exitCode = 1;
|