Add advanced Git and deployment workflows, secure backups and auditing, live Gitea integration, desktop notifications, connection validation, and the premium responsive UX refresh.
92 lines
3.8 KiB
JavaScript
92 lines
3.8 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 = '', severity = 'required') {
|
|
const status = ok ? 'pass' : severity === 'warning' ? 'warning' : 'fail';
|
|
checks.push({ id, name, status, ok: ok || severity === 'warning', detail, help, severity });
|
|
}
|
|
|
|
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; commits will remain disabled until configured', 'Configure git config --global user.name and user.email.', 'warning');
|
|
} 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 blockingChecks = checks.filter((check) => check.status === 'fail');
|
|
|
|
const report = {
|
|
product: 'ForgeFlow',
|
|
version: packageJson.version,
|
|
generatedAt: new Date().toISOString(),
|
|
platform: process.platform,
|
|
arch: process.arch,
|
|
ready: blockingChecks.length === 0,
|
|
checks
|
|
};
|
|
|
|
if (jsonMode) console.log(JSON.stringify(report, null, 2));
|
|
else {
|
|
console.log('ForgeFlow doctor\n');
|
|
for (const check of checks) console.log(`${check.status === 'pass' ? 'PASS' : check.status === 'warning' ? 'WARN' : '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;
|