forked from akai/readest
140 lines
3.8 KiB
JavaScript
140 lines
3.8 KiB
JavaScript
import { spawn, spawnSync } from 'node:child_process';
|
|
import fs from 'node:fs';
|
|
import http from 'node:http';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const appRoot = path.resolve(__dirname, '..');
|
|
const toolRoot = path.join(appRoot, 'tools', 'epub-review-editor');
|
|
const reviewRoot = path.resolve(process.env.READEST_REVIEW_ROOT || path.join(appRoot, 'epub_review_sessions'));
|
|
const defaultPort = Number(process.env.READEST_REVIEW_PORT || 5177);
|
|
const venvRoot = path.join(toolRoot, '.venv');
|
|
const venvPython =
|
|
process.platform === 'win32'
|
|
? path.join(venvRoot, 'Scripts', 'python.exe')
|
|
: path.join(venvRoot, 'bin', 'python');
|
|
|
|
const run = (command, args, options = {}) =>
|
|
spawnSync(command, args, {
|
|
cwd: options.cwd,
|
|
encoding: 'utf8',
|
|
timeout: options.timeout ?? 30_000,
|
|
windowsHide: true,
|
|
});
|
|
|
|
const pythonCandidates =
|
|
process.platform === 'win32'
|
|
? [
|
|
['py', ['-3']],
|
|
['python', []],
|
|
['python3', []],
|
|
]
|
|
: [
|
|
['python3', []],
|
|
['python', []],
|
|
];
|
|
|
|
const findPython = () => {
|
|
for (const [command, args] of pythonCandidates) {
|
|
const result = run(command, [...args, '--version'], { timeout: 10_000 });
|
|
if (result.status === 0) return { command, args };
|
|
}
|
|
throw new Error('Python 3 was not found.');
|
|
};
|
|
|
|
const requestJson = (port, pathname) =>
|
|
new Promise((resolve) => {
|
|
const req = http.get({ host: '127.0.0.1', port, path: pathname, timeout: 800 }, (res) => {
|
|
let body = '';
|
|
res.setEncoding('utf8');
|
|
res.on('data', (chunk) => {
|
|
body += chunk;
|
|
});
|
|
res.on('end', () => {
|
|
try {
|
|
resolve(res.statusCode === 200 ? JSON.parse(body) : null);
|
|
} catch {
|
|
resolve(null);
|
|
}
|
|
});
|
|
});
|
|
req.on('timeout', () => {
|
|
req.destroy();
|
|
resolve(null);
|
|
});
|
|
req.on('error', () => resolve(null));
|
|
});
|
|
|
|
const findRunning = async () => {
|
|
for (let port = defaultPort; port < defaultPort + 100; port++) {
|
|
const payload = await requestJson(port, '/api/version');
|
|
if (payload?.version) return `http://localhost:${port}`;
|
|
}
|
|
return '';
|
|
};
|
|
|
|
if (!fs.existsSync(path.join(toolRoot, 'server.py'))) {
|
|
throw new Error(`Missing review editor server: ${path.join(toolRoot, 'server.py')}`);
|
|
}
|
|
|
|
const existing = await findRunning();
|
|
if (existing) {
|
|
console.log(`EPUB review editor is already running: ${existing}`);
|
|
process.exit(0);
|
|
}
|
|
|
|
if (!fs.existsSync(venvPython)) {
|
|
console.log('Creating review editor Python environment...');
|
|
const python = findPython();
|
|
const result = run(python.command, [...python.args, '-m', 'venv', venvRoot], {
|
|
cwd: appRoot,
|
|
timeout: 120_000,
|
|
});
|
|
if (result.status !== 0) {
|
|
throw new Error(result.stderr || result.stdout || 'Failed to create virtual environment.');
|
|
}
|
|
}
|
|
|
|
const flaskCheck = run(
|
|
venvPython,
|
|
['-c', 'import importlib.util, sys; sys.exit(0 if importlib.util.find_spec("flask") else 1)'],
|
|
{ cwd: toolRoot, timeout: 30_000 },
|
|
);
|
|
|
|
if (flaskCheck.status !== 0) {
|
|
console.log('Installing review editor dependencies...');
|
|
const install = run(venvPython, ['-m', 'pip', 'install', '-r', path.join(toolRoot, 'requirements.txt')], {
|
|
cwd: toolRoot,
|
|
timeout: 180_000,
|
|
});
|
|
if (install.status !== 0) {
|
|
throw new Error(install.stderr || install.stdout || 'Failed to install dependencies.');
|
|
}
|
|
}
|
|
|
|
fs.mkdirSync(reviewRoot, { recursive: true });
|
|
|
|
const child = spawn(
|
|
venvPython,
|
|
[
|
|
path.join(toolRoot, 'server.py'),
|
|
'--review-root',
|
|
reviewRoot,
|
|
'--host',
|
|
'127.0.0.1',
|
|
'--port',
|
|
String(defaultPort),
|
|
'--no-browser',
|
|
],
|
|
{
|
|
cwd: toolRoot,
|
|
stdio: 'inherit',
|
|
windowsHide: true,
|
|
},
|
|
);
|
|
|
|
child.on('exit', (code) => {
|
|
process.exit(code ?? 0);
|
|
});
|