138 lines
4.3 KiB
JavaScript
138 lines
4.3 KiB
JavaScript
import express from 'express';
|
|
import { execFile } from 'node:child_process';
|
|
import { mkdtemp, writeFile, rm } from 'node:fs/promises';
|
|
import { join } from 'node:path';
|
|
import { tmpdir } from 'node:os';
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3001;
|
|
|
|
app.use(express.json({ limit: '1mb' }));
|
|
|
|
// ─── Helpers ─────────────────────────────────────────────
|
|
|
|
const CLI = process.env.ARDUINO_CLI || 'arduino-cli';
|
|
|
|
function run(args, timeout = 120_000) {
|
|
return new Promise((resolve, reject) => {
|
|
execFile(CLI, args, { timeout }, (err, stdout, stderr) => {
|
|
if (err) {
|
|
const msg = stderr?.trim() || stdout?.trim() || err.message;
|
|
reject(new Error(msg));
|
|
} else {
|
|
resolve({ stdout, stderr });
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
async function writeSketchDir(code) {
|
|
const dir = await mkdtemp(join(tmpdir(), 'arduino-sketch-'));
|
|
const sketchDir = join(dir, 'sketch');
|
|
const { mkdir } = await import('node:fs/promises');
|
|
await mkdir(sketchDir, { recursive: true });
|
|
await writeFile(join(sketchDir, 'sketch.ino'), code, 'utf-8');
|
|
return { dir, sketchDir };
|
|
}
|
|
|
|
async function cleanupDir(dir) {
|
|
try {
|
|
await rm(dir, { recursive: true, force: true });
|
|
} catch {
|
|
// best-effort cleanup
|
|
}
|
|
}
|
|
|
|
// ─── Routes ──────────────────────────────────────────────
|
|
|
|
app.get('/api/arduino/status', async (_req, res) => {
|
|
try {
|
|
const { stdout } = await run(['version', '--format', 'json']);
|
|
const info = JSON.parse(stdout);
|
|
res.json({ ok: true, version: info.VersionString || info.version || stdout.trim() });
|
|
} catch (err) {
|
|
res.status(503).json({ ok: false, error: `arduino-cli not available: ${err.message}` });
|
|
}
|
|
});
|
|
|
|
app.get('/api/arduino/boards', async (_req, res) => {
|
|
try {
|
|
const { stdout } = await run(['board', 'list', '--format', 'json']);
|
|
const raw = JSON.parse(stdout);
|
|
|
|
// arduino-cli >=0.35 returns { detected_ports: [...] }
|
|
// older versions return a flat array
|
|
const ports = Array.isArray(raw) ? raw : (raw.detected_ports || []);
|
|
|
|
const boards = ports
|
|
.filter(p => p.port)
|
|
.map(entry => {
|
|
const port = entry.port?.address || entry.port?.label || entry.address || '';
|
|
const matchingBoards = entry.matching_boards || entry.boards || [];
|
|
const first = matchingBoards[0] || {};
|
|
return {
|
|
port,
|
|
name: first.name || entry.port?.protocol_label || 'Unknown',
|
|
fqbn: first.fqbn || '',
|
|
};
|
|
})
|
|
.filter(b => b.port);
|
|
|
|
res.json(boards);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
app.post('/api/arduino/compile', async (req, res) => {
|
|
const { code } = req.body;
|
|
if (!code) return res.status(400).json({ error: 'No code provided' });
|
|
|
|
let dir;
|
|
try {
|
|
({ dir } = await writeSketchDir(code));
|
|
const sketchDir = join(dir, 'sketch');
|
|
const { stdout, stderr } = await run([
|
|
'compile',
|
|
'--fqbn', req.body.fqbn || 'arduino:avr:uno',
|
|
sketchDir,
|
|
]);
|
|
res.json({ ok: true, output: stdout + stderr });
|
|
} catch (err) {
|
|
res.status(400).json({ ok: false, error: err.message });
|
|
} finally {
|
|
if (dir) cleanupDir(dir);
|
|
}
|
|
});
|
|
|
|
app.post('/api/arduino/upload', async (req, res) => {
|
|
const { code, port, fqbn } = req.body;
|
|
if (!code) return res.status(400).json({ error: 'No code provided' });
|
|
if (!port) return res.status(400).json({ error: 'No port specified' });
|
|
|
|
let dir;
|
|
try {
|
|
({ dir } = await writeSketchDir(code));
|
|
const sketchDir = join(dir, 'sketch');
|
|
const { stdout, stderr } = await run([
|
|
'compile',
|
|
'--upload',
|
|
'--fqbn', fqbn || 'arduino:avr:uno',
|
|
'--port', port,
|
|
sketchDir,
|
|
]);
|
|
res.json({ ok: true, output: stdout + stderr });
|
|
} catch (err) {
|
|
res.status(400).json({ ok: false, error: err.message });
|
|
} finally {
|
|
if (dir) cleanupDir(dir);
|
|
}
|
|
});
|
|
|
|
// ─── Start ───────────────────────────────────────────────
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`Arduino CLI server listening on http://localhost:${PORT}`);
|
|
console.log(`Using CLI: ${CLI}`);
|
|
});
|