diff --git a/cli/src/devtools.ts b/cli/src/devtools.ts index 07cc9099c7..206467ebd1 100644 --- a/cli/src/devtools.ts +++ b/cli/src/devtools.ts @@ -111,7 +111,7 @@ export async function devtools( const traceFd = options.trace ? await open(options.trace, "w") : null // passive bus to sniff packets - const transports = createTransports(options) + const transports = await createTransports(options) const bus = new JDBus(transports, { client: false, disableRoleManager: true, @@ -387,7 +387,7 @@ function startDbgServer(port: number, options: DevToolsOptions) { } async function connectCmd(req: ConnectReqArgs) { - await connectTransport(devtoolsSelf.bus, req) + await connectTransport(devtoolsSelf, req) } async function buildCmd(args: BuildReqArgs) { diff --git a/cli/src/sideprotocol.ts b/cli/src/sideprotocol.ts index acd9ae7ca8..b0a3d3bcd6 100644 --- a/cli/src/sideprotocol.ts +++ b/cli/src/sideprotocol.ts @@ -140,6 +140,9 @@ export interface SideAddBoardResp extends SideResp<"addBoard"> { export interface SideTransportEvent extends SideEvent<"transport"> { data: TransportStatus } +export interface SideMissingPackageEvent extends SideEvent<"missingPackage"> { + packageName: string +} export interface TransportStatus { autoConnect?: boolean diff --git a/cli/src/transport.ts b/cli/src/transport.ts index 5aa775c092..05336884e6 100644 --- a/cli/src/transport.ts +++ b/cli/src/transport.ts @@ -14,6 +14,7 @@ import { import { DevToolsIface, sendEvent } from "./sidedata" import { ConnectReqArgs, + SideMissingPackageEvent, SideTransportEvent, TransportStatus, WebSocketConnectReqArgs, @@ -33,29 +34,48 @@ export interface TransportsOptions { spi?: boolean } -function tryRequire(name: string) { - return require(name) +export class RequireError extends Error { + constructor(readonly packageName: string) { + super(`failed to require package "${packageName}"`) + } +} + +interface RequireOptions { + interactive?: boolean } -function createSPI() { +async function tryRequire(name: string, options: RequireOptions) { + try { + return require(name) + } catch (e) { + log(`failed to require package "${name}"`) + console.debug(e.stderr?.toString()) + if (options.interactive) { + // TODO + } + throw new RequireError(name) + } +} + +async function createSPI(options?: RequireOptions) { log(`adding SPI transport (requires "rpio" package)`) // eslint-disable-next-line @typescript-eslint/no-var-requires - const RPIO = tryRequire("rpio") + const RPIO = await tryRequire("rpio", options) // eslint-disable-next-line @typescript-eslint/no-var-requires - const SpiDev = tryRequire("spi-device") + const SpiDev = await tryRequire("spi-device", options) return createNodeSPITransport(RPIO, SpiDev) } -function createUSB() { +async function createUSB(options?: RequireOptions) { log(`adding USB transport (requires "usb" package)`) - const usb = tryRequire("usb") - const options = createNodeUSBOptions(usb.WebUSB) - return createUSBTransport(options) + const usb = await tryRequire("usb", options) + const usbOptions = createNodeUSBOptions(usb.WebUSB) + return createUSBTransport(usbOptions) } -function createSerial() { +async function createSerial(options?: RequireOptions) { log(`adding serial transport (requires "serialport" package)`) // eslint-disable-next-line @typescript-eslint/no-var-requires - const SerialPort = tryRequire("serialport").SerialPort - return createNodeWebSerialTransport(SerialPort) + const sp = await tryRequire("serialport", options) + return createNodeWebSerialTransport(sp.SerialPort) } function createWebSocket(url: string, protocol: string) { @@ -90,7 +110,11 @@ function createWebSocket(url: string, protocol: string) { return transport } -export async function connectTransport(bus: JDBus, req: ConnectReqArgs) { +export async function connectTransport( + devtools: DevToolsIface, + req: ConnectReqArgs +) { + const bus = devtools.bus const { transport: type, background, resourceGroupId } = req // no type, reconnect all if (!type) { @@ -110,28 +134,38 @@ export async function connectTransport(bus: JDBus, req: ConnectReqArgs) { // need to start transport let newTransport: Transport - switch (type) { - case "websocket": { - const { url, protocol } = req as WebSocketConnectReqArgs - newTransport = createWebSocket(url, protocol) - break - } - case "spi": { - newTransport = createSPI() - break - } - case "serial": { - newTransport = createSerial() - break - } - case "usb": { - newTransport = createUSB() - break - } - case "none": { - // disconnect - break + try { + switch (type) { + case "websocket": { + const { url, protocol } = req as WebSocketConnectReqArgs + newTransport = createWebSocket(url, protocol) + break + } + case "spi": { + newTransport = await createSPI() + break + } + case "serial": { + newTransport = await createSerial() + break + } + case "usb": { + newTransport = await createUSB() + break + } + case "none": { + // disconnect + break + } } + } catch (e) { + if (e instanceof RequireError) { + sendEvent( + devtools.mainClient, + "missingPackage", + { packageName: e.packageName } + ) + } else throw e } if (newTransport) { @@ -142,11 +176,12 @@ export async function connectTransport(bus: JDBus, req: ConnectReqArgs) { await Promise.all(bus.transports.map(tr => tr.connect(background))) } -export function createTransports(options: TransportsOptions) { +export async function createTransports(options: TransportsOptions) { const transports: Transport[] = [] - if (options.usb) transports.push(createUSB()) - if (options.serial) transports.push(createSerial()) - if (options.spi) transports.push(createSPI()) + if (options.usb) transports.push(await createUSB({ interactive: true })) + if (options.serial) + transports.push(await createSerial({ interactive: true })) + if (options.spi) transports.push(await createSPI({ interactive: true })) return transports } diff --git a/interop/src/errors.ts b/interop/src/errors.ts index 9b9f301147..bb7b999e41 100644 --- a/interop/src/errors.ts +++ b/interop/src/errors.ts @@ -1,11 +1,10 @@ // generated file, run scripts/builderrors.mjs to update export const errors: Record = { - "loopback rx ovf": "loopback-rx-ovf", - "can't connect, no HF2 nor JDUSB": "no-hf2", - "esptool cannot connect": "esptool-cannot-connect", - "I2C device not found or malfunctioning": - "i2c-device-not-found-or-malfunctioning", - "Unable to locate Node.JS v16+.": "terminal-nodemissing", - "Install @devicescript/cli package": "terminal-notinstalled", - 'missing "devicescript" section': "missing-devicescript-section", -} + "loopback rx ovf": "loopback-rx-ovf", + "can't connect, no HF2 nor JDUSB": "no-hf2", + "esptool cannot connect": "esptool-cannot-connect", + "I2C device not found or malfunctioning": "i2c-device-not-found-or-malfunctioning", + "Unable to locate Node.JS v16+.": "terminal-nodemissing", + "Install @devicescript/cli package": "terminal-notinstalled", + "missing \"devicescript\" section": "missing-devicescript-section" +}; diff --git a/jacdac-ts b/jacdac-ts index 7c03d7bbba..fb222f68f4 160000 --- a/jacdac-ts +++ b/jacdac-ts @@ -1 +1 @@ -Subproject commit 7c03d7bbba80baad837f8604f764b5a40d4ee6d8 +Subproject commit fb222f68f44227593666dc4c193499a1ed22288c diff --git a/vscode/src/state.ts b/vscode/src/state.ts index 28f8b00764..2f95d187b0 100644 --- a/vscode/src/state.ts +++ b/vscode/src/state.ts @@ -35,6 +35,7 @@ import { SideAddTestReq, SideAddTestResp, SideConnectReq, + SideMissingPackageEvent, SideStartVmReq, SideStopVmReq, SideTransportEvent, @@ -52,7 +53,6 @@ import { SimulatorsWebView } from "./simulatorWebView" import { showErrorMessage } from "./telemetry" import _serverInfo from "./server-info.json" import { resolvePythonEnvironment } from "./python" -import { resolveDarkMode } from "./assets" const serverInfo = _serverInfo as ServerInfoFile @@ -106,6 +106,13 @@ export class DeviceScriptExtensionState extends JDEventSource { this.emit(CHANGE) } }) + subSideEvent("missingPackage", async msg => { + const packageName = msg.packageName + await showErrorMessage( + "package.missing", + `Failed to require ${packageName} package. Please install manually.` + ) + }) } get state() {