Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update PR for https://github.com/microsoft/devicescript/pull/653 #656

Merged
merged 4 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions cli/src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ import {
toHex,
versionTryParse,
} from "jacdac-ts"
import { execSync } from "node:child_process"
import { BuildOptions } from "./sideprotocol"
import { readJSON5Sync } from "./jsonc"
import { execCmd } from "./exec"

// TODO should we move this to jacdac-ts and call automatically for transports?
export function setupWebsocket() {
Expand Down Expand Up @@ -216,14 +216,6 @@ function toDevsDiag(d: jdspec.Diagnostic): DevsDiagnostic {
}
}

function execCmd(cmd: string) {
try {
return execSync(cmd, { encoding: "utf-8" }).trim()
} catch {
return ""
}
}

function isGit() {
let pref = ""
for (let i = 0; i < 10; ++i) {
Expand Down
6 changes: 6 additions & 0 deletions cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
incVerbose,
setConsoleColors,
setDeveloperMode,
setInteractive,
setQuiet,
verboseLog,
} from "./command"
Expand Down Expand Up @@ -75,6 +76,10 @@ export async function mainCli() {
.version(cliVersion())
.option("-v, --verbose", "more logging (can be repeated)")
.option("--quiet", "less logging")
.option(
"--ci",
"disable interactions with user, default is false unless CI env var is set"
)
.option("--no-colors", "disable color output")
.option("--dev", "developer mode")

Expand Down Expand Up @@ -429,6 +434,7 @@ export async function mainCli() {
.action(binPatch)

program.on("option:quiet", () => setQuiet(true))
program.on("option:ci", () => setInteractive(false))
program.on("option:verbose", incVerbose)
program.on("option:no-colors", () => setConsoleColors(false))
program.on("option:dev", () => {
Expand Down
5 changes: 5 additions & 0 deletions cli/src/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export function logToConsole(priority: LoggerPriority, message: string) {

export let isVerbose = 0
export let isQuiet = false
export let isInteractive = !Boolean(process.env.CI)

export function incVerbose() {
isVerbose++
Expand All @@ -80,6 +81,10 @@ export function setQuiet(v: boolean) {
isQuiet = v
}

export function setInteractive(v: boolean) {
isInteractive = v
}

export function verboseLog(msg: string) {
if (isVerbose) console.debug(wrapColor(90, msg))
}
3 changes: 2 additions & 1 deletion cli/src/devtools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const WebSocket = require("faye-websocket")
import http from "http"
import url from "url"
import net from "net"
import { error, log } from "./command"
import { error, isInteractive, log, setInteractive } from "./command"
import { watch } from "fs-extra"
import { resolveBuildConfig, SrcFile } from "@devicescript/compiler"
import {
Expand Down Expand Up @@ -100,6 +100,7 @@ export async function devtools(
const tcpPort = 8082
const dbgPort = 8083

if (options.vscode) setInteractive(false) // don't prompt for anything
if (options.diagnostics) Flags.diagnostics = true

overrideConsoleDebug()
Expand Down
9 changes: 9 additions & 0 deletions cli/src/exec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { execSync } from "node:child_process"

export function execCmd(cmd: string) {
try {
return execSync(cmd, { encoding: "utf-8" }).trim()
} catch {
return ""
}
}
11 changes: 2 additions & 9 deletions cli/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
existsSync,
} from "fs-extra"
import { build } from "./build"
import { spawnSync, execSync } from "node:child_process"
import { spawnSync } from "node:child_process"
import { assert, clone, randomUInt } from "jacdac-ts"
import { addReqHandler } from "./sidedata"
import type {
Expand All @@ -32,6 +32,7 @@ import { addBoard } from "./addboard"
import { readJSON5Sync } from "./jsonc"
import { MARKETPLACE_EXTENSION_ID } from "@devicescript/interop"
import { TSDOC_TAGS } from "@devicescript/compiler"
import { execCmd } from "./exec"

const MAIN = "src/main.ts"
const GITIGNORE = ".gitignore"
Expand Down Expand Up @@ -570,14 +571,6 @@ export interface AddNpmOptions extends InitOptions {

export interface AddSettingsOptions extends InitOptions {}

export function execCmd(cmd: string) {
try {
return execSync(cmd, { encoding: "utf-8" }).trim()
} catch {
return ""
}
}

export async function addSettings(options: AddSettingsOptions) {
const files = clone(settingsFiles)
const cwd = writeFiles(".", options, files)
Expand Down
2 changes: 0 additions & 2 deletions cli/src/logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import {
JDBus,
Packet,
PACKET_REPORT,
SRV_DEVICE_SCRIPT_MANAGER,
DeviceScriptManagerCmd,
LoggerPriority,
SRV_LOGGER,
LoggerCmd,
Expand Down
95 changes: 95 additions & 0 deletions cli/src/packageinstaller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { readFile, constants } from "node:fs/promises"
import { accessSync } from "node:fs"
import { join } from "node:path"
import { spawn } from "node:child_process"
import { isInteractive, verboseLog } from "./command"
import { PkgJson } from "@devicescript/compiler"
import { consoleBooleanAsk } from "./prompt"

function isYarnRepo(): boolean {
try {
accessSync(join(process.cwd(), "yarn.lock"), constants.R_OK)
return true
} catch {
return false
}
}

function getPackageInstallerCommand(
packageName?: string
): string[] {
if (isYarnRepo()) {
if (!packageName) return ["yarn", "install"]

return ["yarn", "add", packageName]
}

if (!packageName) return ["npm", "install"]

return ["npm", "install", "--save", "--no-workspaces", packageName]
}

async function isPackageInstalledLocally(pkgName: string): Promise<boolean> {
const pkgJsonString = await readFile(join(process.cwd(), "package.json"), {
encoding: "utf-8",
})
const pkgJson = JSON.parse(pkgJsonString) as PkgJson

return Object.keys(pkgJson.dependencies).includes(pkgName)
}

async function spawnAsyncInstaller(packageName: string) {
let resolve: () => void
let reject: () => void

const [rootCmd, ...cmdArgs] = getPackageInstallerCommand(packageName)

const installProcess = spawn(rootCmd, cmdArgs, {
cwd: process.cwd(),
env: process.env,
})

installProcess.stderr.on("data", data => {
verboseLog(`package installer process: ${data}`)
})

installProcess.on("close", code => {
verboseLog(`install process exit with code ${code}`)

if (code === 0) resolve()
else reject()
})

return new Promise((res, rej) => {
// @ts-ignore
resolve = res
reject = rej
})
}

export async function askForPackageInstallation(
pkgName: string,
installByDefault = true
) {
if (!isInteractive) return

if (await isPackageInstalledLocally(pkgName)) return

const shouldInstallPackage = await consoleBooleanAsk(
`Install package "${pkgName}"`,
installByDefault
)

if (shouldInstallPackage) {
try {
console.log(`Installing package "${pkgName}" ...`)
await spawnAsyncInstaller(pkgName)
console.log(`Package "${pkgName}" installed!`)
} catch (e) {
const installCmd = getPackageInstallerCommand(pkgName).join(" ")
console.log(
`Automatic package installation failed :( You can try to install it manually by running "${installCmd}"`
)
}
}
}
29 changes: 29 additions & 0 deletions cli/src/prompt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export function consoleBooleanAsk(
question: string,
defValue = true
): Promise<boolean> {
return new Promise(resolve => {
const ask = (q: string) =>
process.stdout.write(`${q} ${defValue ? "(Y/n)" : "(y/N)"}?`)
const handler = (data: Buffer) => {
const response = data.toString().trim().toLowerCase()
if (response === "y") {
process.stdin.removeListener("data", handler)
resolve(true)
} else if (response === "n") {
process.stdin.removeListener("data", handler)
process.stdin.end()
resolve(false)
} else if (response === "") {
process.stdin.removeListener("data", handler)
resolve(defValue)
} else {
ask(
`Unknown option: ${response}. Use y/n and try again \n${question}`
)
}
}
process.stdin.addListener("data", handler)
ask(question)
})
}
35 changes: 16 additions & 19 deletions cli/src/transport.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { log } from "./command"
import { isInteractive, log } from "./command"
import {
CONNECTION_STATE,
createNodeSPITransport,
Expand Down Expand Up @@ -27,6 +27,7 @@ import type {
SideUploadJsonFromDevice,
} from "@devicescript/interop"
import { printDmesg } from "./vmworker"
import { askForPackageInstallation } from "./packageinstaller"

export interface TransportsOptions {
usb?: boolean
Expand All @@ -40,41 +41,38 @@ export class RequireError extends Error {
}
}

interface RequireOptions {
interactive?: boolean
}

async function tryRequire(name: string, options: RequireOptions) {
async function tryRequire(name: string) {
try {
return require(name)
} catch (e) {
log(`failed to require package "${name}"`)
console.debug(e.stderr?.toString())
if (options.interactive) {
// TODO
if (isInteractive) {
await askForPackageInstallation(name)
return require(name)
}
throw new RequireError(name)
}
}

async function createSPI(options?: RequireOptions) {
async function createSPI() {
log(`adding SPI transport (requires "rpio" package)`)
// eslint-disable-next-line @typescript-eslint/no-var-requires
const RPIO = await tryRequire("rpio", options)
const RPIO = await tryRequire("rpio")
// eslint-disable-next-line @typescript-eslint/no-var-requires
const SpiDev = await tryRequire("spi-device", options)
const SpiDev = await tryRequire("spi-device")
return createNodeSPITransport(RPIO, SpiDev)
}
async function createUSB(options?: RequireOptions) {
async function createUSB() {
log(`adding USB transport (requires "usb" package)`)
const usb = await tryRequire("usb", options)
const usb = await tryRequire("usb")
const usbOptions = createNodeUSBOptions(usb.WebUSB)
return createUSBTransport(usbOptions)
}
async function createSerial(options?: RequireOptions) {
async function createSerial() {
log(`adding serial transport (requires "serialport" package)`)
// eslint-disable-next-line @typescript-eslint/no-var-requires
const sp = await tryRequire("serialport", options)
const sp = await tryRequire("serialport")
return createNodeWebSerialTransport(sp.SerialPort)
}

Expand Down Expand Up @@ -178,10 +176,9 @@ export async function connectTransport(

export async function createTransports(options: TransportsOptions) {
const transports: Transport[] = []
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 }))
if (options.usb) transports.push(await createUSB())
if (options.serial) transports.push(await createSerial())
if (options.spi) transports.push(await createSPI())
return transports
}

Expand Down
1 change: 1 addition & 0 deletions interop/src/archconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ export interface PkgJson {
library?: boolean
bundle?: boolean
}
dependencies?: Record<string, string>
}

export interface LocalBuildConfig {
Expand Down