import os from 'node:os'; import fs from 'node:fs'; import path from 'node:path'; import fsExtra from 'fs-extra'; import fsPlus from 'fs-plus'; import fetch from 'node-fetch'; import decompress from 'decompress'; import decompressUnzip from 'decompress-unzip'; import * as tar from 'tar'; import shell from 'shelljs'; const CLI_VERSION = '1.3.1'; const ARDUINO_AVR_VERSION = '1.8.6'; const ARDUINO_ESP8266_VERSION = '3.1.1'; const ARDUINO_ESP32_VERSION = '2.0.15-cn'; const CLI_DIR_PATH = path.resolve(process.cwd(), 'arduino-cli'); class ArduinoCLI { static getDonwloadUrl(version) { const platform = os.platform(); const arch = os.arch(); let filename = ''; if (platform === 'win32') { if (arch === 'x64') { filename = `arduino-cli_${version}_Windows_64bit.zip`; } else if (arch === 'ia32') { filename = `arduino-cli_${version}_Windows_32bit.zip`; } } else if (platform === 'darwin') { if (arch === 'x64') { filename = `arduino-cli_${version}_macOS_64bit.tar.gz`; } else if (arch === 'arm64') { filename = `arduino-cli_${version}_macOS_ARM64.tar.gz`; } } else if (platform === 'linux') { if (arch === 'x64') { filename = `arduino-cli_${version}_Linux_64bit.tar.gz`; } else if (arch === 'ia32') { filename = `arduino-cli_${version}_Linux_32bit.tar.gz`; } else if (arch === 'arm64') { filename = `arduino-cli_${version}_Linux_ARM64.tar.gz`; } else if (arch === 'arm') { const { stdout } = shell.exec('uname -m', { silent: true }); if (stdout === 'armv6l') { filename = `arduino-cli_${version}_Linux_ARMv6.tar.gz`; } else if (stdout === 'armv7l') { filename = `arduino-cli_${version}_Linux_ARMv7.tar.gz`; } } } if (!filename) { throw new Error('The current system or architecture is not supported'); } return `https://downloads.arduino.cc/arduino-cli/${filename}`; } #version_ = ''; #dirPath_ = ''; #cliPath_ = ''; #configPath_ = ''; #tempPath_ = ''; #boards_ = {}; constructor(version, dirPath) { this.#version_ = version; this.#dirPath_ = dirPath; this.#tempPath_ = path.resolve(dirPath, 'temp'); fsExtra.ensureDirSync(dirPath); fsExtra.ensureDirSync(this.#tempPath_); if (os.platform() === 'win32') { this.#cliPath_ = path.resolve(dirPath, 'arduino-cli.exe'); } else { this.#cliPath_ = path.resolve(dirPath, 'arduino-cli'); } this.#configPath_ = path.resolve(dirPath, 'arduino-cli.json'); } async init() { const config = await fsExtra.readJson(path.resolve(process.cwd(), 'arduino-cli.json')); config.directories = { data: path.resolve(this.#dirPath_, 'Arduino15'), downloads: path.resolve(this.#dirPath_, 'staging'), user: path.resolve(this.#dirPath_, 'Arduino') }; await fsExtra.writeJson(this.#configPath_, config, { spaces: ' ' }); if (!this.available()) { await this.install(); } shell.exec(`"${this.#cliPath_}" core update-index --config-file "${this.#configPath_}"`); this.updateBoardsStatus(); } updateBoardsStatus() { const { stdout } = shell.exec(`"${this.#cliPath_}" core list --json --config-file "${this.#configPath_}"`, { silent: true }); const info = JSON.parse(stdout); const { platforms } = info; this.#boards_ = {}; for (let board of platforms) { this.#boards_[board.id] = board.installed_version; } } available() { let available = false; if (fsPlus.isFileSync(this.#cliPath_)) { const { stdout } = shell.exec(`"${this.#cliPath_}" version --json --config-file "${this.#configPath_}"`, { silent: true }); const info = JSON.parse(stdout); if (info.VersionString === this.#version_) { available = true; } } return available; } async coreInstall(boardKey, version) { if (this.#boards_[boardKey] === version) { console.log(`Platform ${boardKey}@${version} already installed`); return; } shell.exec(`"${this.#cliPath_}" core install ${boardKey}@${version} --config-file "${this.#configPath_}"`); } async install() { const url = ArduinoCLI.getDonwloadUrl(this.#version_); console.log(`Start Download: ${url}`); const res = await fetch(url); if (!res.ok) { throw new Error(`Download failed: ${res.statusText}`); } const cliTempPath = path.resolve(this.#tempPath_, path.basename(url)); const fileStream = fs.createWriteStream(cliTempPath); await new Promise((resolve, reject) => { res.body.pipe(fileStream); res.body.on('error', reject); fileStream.on('finish', resolve); }); console.log('Download complete, start to unzip...'); if (url.endsWith('.zip')) { await decompress(cliTempPath, this.#dirPath_, { plugins: [decompressUnzip()] }); } else { await tar.x({ file: cliTempPath, cwd: this.#dirPath_, strict: true }); } fs.unlinkSync(cliTempPath); console.log('Unzip successfully, the arduino-cli is ready'); } } const arduino = new ArduinoCLI(CLI_VERSION, CLI_DIR_PATH); await arduino.init(); arduino.coreInstall('arduino:avr', ARDUINO_AVR_VERSION); arduino.coreInstall('esp8266:esp8266', ARDUINO_ESP8266_VERSION); arduino.coreInstall('esp32:esp32', ARDUINO_ESP32_VERSION);