xs-dev 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/package.json
CHANGED
|
@@ -1,94 +1,15 @@
|
|
|
1
|
-
import { setTimeout as sleep } from 'node:timers/promises';
|
|
2
|
-
import { existsSync, statSync } from 'node:fs';
|
|
3
|
-
import { execSync } from 'node:child_process';
|
|
4
1
|
import { buildCommand } from '@stricli/core';
|
|
5
|
-
import { SerialPort } from 'serialport';
|
|
6
|
-
import { findBySerialNumber } from 'usb';
|
|
7
2
|
import ora from 'ora';
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
import * as output from '../lib/output.js';
|
|
3
|
+
import { handleEvent } from '../lib/renderer.js';
|
|
4
|
+
import scanDevices from '../toolbox/scan/index.js';
|
|
11
5
|
const command = buildCommand({
|
|
12
6
|
docs: {
|
|
13
7
|
brief: 'Look for available devices for deployment',
|
|
14
8
|
},
|
|
15
9
|
async func() {
|
|
16
10
|
const spinner = ora();
|
|
17
|
-
await
|
|
18
|
-
|
|
19
|
-
existsSync(process.env.IDF_PATH) &&
|
|
20
|
-
statSync(process.env.IDF_PATH).isDirectory()) {
|
|
21
|
-
spinner.start(`Found ESP_IDF, sourcing environment...`);
|
|
22
|
-
await sourceIdf();
|
|
23
|
-
spinner.stop();
|
|
24
|
-
}
|
|
25
|
-
const esptoolPath = (() => {
|
|
26
|
-
try {
|
|
27
|
-
const result = execSync('which esptool.py', { encoding: 'utf8' }).trim();
|
|
28
|
-
return result.length > 0 ? result : null;
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
})();
|
|
34
|
-
if (esptoolPath === null) {
|
|
35
|
-
output.warn('esptool.py required to scan for Espressif devices. Setup environment for ESP8266 or ESP32:\n xs-dev setup --device esp32\n xs-dev setup --device esp8266.');
|
|
36
|
-
}
|
|
37
|
-
const picotoolPath = (() => {
|
|
38
|
-
try {
|
|
39
|
-
const result = execSync('which picotool', { encoding: 'utf8' }).trim();
|
|
40
|
-
return result.length > 0 ? result : null;
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
})();
|
|
46
|
-
const hasPicotool = picotoolPath !== null;
|
|
47
|
-
if (hasPicotool) {
|
|
48
|
-
try {
|
|
49
|
-
spinner.start('Found picotool, rebooting device before scanning');
|
|
50
|
-
execSync('picotool reboot -fa');
|
|
51
|
-
await sleep(1000);
|
|
52
|
-
spinner.stop();
|
|
53
|
-
}
|
|
54
|
-
catch { }
|
|
55
|
-
}
|
|
56
|
-
spinner.start('Scanning for devices...');
|
|
57
|
-
const ports = await SerialPort.list();
|
|
58
|
-
const result = await Promise.all(ports
|
|
59
|
-
.filter((port) => port.serialNumber !== undefined)
|
|
60
|
-
.map(async (port) => {
|
|
61
|
-
try {
|
|
62
|
-
if (port.vendorId === '2e8a' && hasPicotool) {
|
|
63
|
-
const device = await findBySerialNumber(port.serialNumber ?? '');
|
|
64
|
-
const bus = String(device?.busNumber);
|
|
65
|
-
const address = String(device?.deviceAddress);
|
|
66
|
-
const buffer = execSync(`picotool info --bus ${bus} --address ${address} -fa`);
|
|
67
|
-
return [buffer, port.path];
|
|
68
|
-
}
|
|
69
|
-
const buffer = execSync(`esptool.py --port ${port.path} read_mac`);
|
|
70
|
-
return [buffer, port.path];
|
|
71
|
-
}
|
|
72
|
-
catch { }
|
|
73
|
-
return [undefined, port.path];
|
|
74
|
-
}));
|
|
75
|
-
const record = parseScanResult(result);
|
|
76
|
-
const rows = Object.keys(record).map((port) => {
|
|
77
|
-
const { device, features } = record[port];
|
|
78
|
-
return [port, device, features];
|
|
79
|
-
});
|
|
80
|
-
if (rows.length === 0) {
|
|
81
|
-
spinner.warn('No available devices found.');
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
spinner.succeed('Found the following available devices!');
|
|
85
|
-
const allRows = [['Port', 'Device', 'Features'], ...rows];
|
|
86
|
-
const colWidths = allRows[0].map((_, colIdx) => Math.max(...allRows.map((row) => String(row[colIdx]).length)));
|
|
87
|
-
for (const row of allRows) {
|
|
88
|
-
output.info(row
|
|
89
|
-
.map((cell, idx) => String(cell).padEnd(colWidths[idx]))
|
|
90
|
-
.join(' '));
|
|
91
|
-
}
|
|
11
|
+
for await (const event of scanDevices()) {
|
|
12
|
+
handleEvent(event, spinner);
|
|
92
13
|
}
|
|
93
14
|
},
|
|
94
15
|
parameters: {
|
|
@@ -96,4 +17,4 @@ const command = buildCommand({
|
|
|
96
17
|
},
|
|
97
18
|
});
|
|
98
19
|
export default command;
|
|
99
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
20
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2Nhbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9jb21tYW5kcy9zY2FuLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxlQUFlLENBQUE7QUFDNUMsT0FBTyxHQUFHLE1BQU0sS0FBSyxDQUFBO0FBRXJCLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQTtBQUNoRCxPQUFPLFdBQVcsTUFBTSwwQkFBMEIsQ0FBQTtBQUVsRCxNQUFNLE9BQU8sR0FBRyxZQUFZLENBQUM7SUFDM0IsSUFBSSxFQUFFO1FBQ0osS0FBSyxFQUFFLDJDQUEyQztLQUNuRDtJQUNELEtBQUssQ0FBQyxJQUFJO1FBQ1IsTUFBTSxPQUFPLEdBQUcsR0FBRyxFQUFFLENBQUE7UUFDckIsSUFBSSxLQUFLLEVBQUUsTUFBTSxLQUFLLElBQUksV0FBVyxFQUFFLEVBQUUsQ0FBQztZQUN4QyxXQUFXLENBQUMsS0FBSyxFQUFFLE9BQU8sQ0FBQyxDQUFBO1FBQzdCLENBQUM7SUFDSCxDQUFDO0lBQ0QsVUFBVSxFQUFFO1FBQ1YsS0FBSyxFQUFFLEVBQUU7S0FDVjtDQUNGLENBQUMsQ0FBQTtBQUVGLGVBQWUsT0FBTyxDQUFBIn0=
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
2
|
+
import { existsSync, statSync } from 'node:fs';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import { SerialPort } from 'serialport';
|
|
5
|
+
import { findBySerialNumber } from 'usb';
|
|
6
|
+
import { parseScanResult } from './parse.js';
|
|
7
|
+
import { sourceEnvironment, sourceIdf } from '../system/exec.js';
|
|
8
|
+
export default async function* scanDevices() {
|
|
9
|
+
const envResult = await sourceEnvironment();
|
|
10
|
+
if (!envResult.success) {
|
|
11
|
+
yield {
|
|
12
|
+
type: 'warning',
|
|
13
|
+
message: `Failed to source environment: ${envResult.error}`,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
if (typeof process.env.IDF_PATH === 'string' &&
|
|
17
|
+
existsSync(process.env.IDF_PATH) &&
|
|
18
|
+
statSync(process.env.IDF_PATH).isDirectory()) {
|
|
19
|
+
yield { type: 'info', message: 'Found ESP_IDF, sourcing environment...' };
|
|
20
|
+
const idfResult = await sourceIdf();
|
|
21
|
+
if (!idfResult.success) {
|
|
22
|
+
yield {
|
|
23
|
+
type: 'warning',
|
|
24
|
+
message: `Failed to source IDF environment: ${idfResult.error}`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const esptoolPath = (() => {
|
|
29
|
+
try {
|
|
30
|
+
const result = execSync('which esptool.py', { encoding: 'utf8' }).trim();
|
|
31
|
+
return result.length > 0 ? result : null;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
})();
|
|
37
|
+
if (esptoolPath === null) {
|
|
38
|
+
yield {
|
|
39
|
+
type: 'warning',
|
|
40
|
+
message: 'esptool.py required to scan for Espressif devices. Setup environment for ESP8266 or ESP32:\n xs-dev setup --device esp32\n xs-dev setup --device esp8266.',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const picotoolPath = (() => {
|
|
44
|
+
try {
|
|
45
|
+
const result = execSync('which picotool', { encoding: 'utf8' }).trim();
|
|
46
|
+
return result.length > 0 ? result : null;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
})();
|
|
52
|
+
const hasPicotool = picotoolPath !== null;
|
|
53
|
+
if (hasPicotool) {
|
|
54
|
+
try {
|
|
55
|
+
yield {
|
|
56
|
+
type: 'step:start',
|
|
57
|
+
message: 'Found picotool, rebooting device before scanning',
|
|
58
|
+
};
|
|
59
|
+
execSync('picotool reboot -fa');
|
|
60
|
+
await sleep(1000);
|
|
61
|
+
yield { type: 'step:done' };
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
yield { type: 'step:done' }; // best-effort reboot, continue scanning
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
yield { type: 'step:start', message: 'Scanning for devices...' };
|
|
68
|
+
const ports = await SerialPort.list();
|
|
69
|
+
const result = await Promise.all(ports
|
|
70
|
+
.filter((port) => port.serialNumber !== undefined)
|
|
71
|
+
.map(async (port) => {
|
|
72
|
+
try {
|
|
73
|
+
if (port.vendorId === '2e8a' && hasPicotool) {
|
|
74
|
+
const device = await findBySerialNumber(port.serialNumber ?? '');
|
|
75
|
+
if (device === null || typeof device === 'undefined') {
|
|
76
|
+
return [undefined, port.path];
|
|
77
|
+
}
|
|
78
|
+
const bus = String(device.busNumber);
|
|
79
|
+
const address = String(device.deviceAddress);
|
|
80
|
+
const buffer = execSync(`picotool info --bus ${bus} --address ${address} -fa`);
|
|
81
|
+
return [buffer, port.path];
|
|
82
|
+
}
|
|
83
|
+
if (esptoolPath === null) {
|
|
84
|
+
return [undefined, port.path];
|
|
85
|
+
}
|
|
86
|
+
const buffer = execSync(`esptool.py --port ${port.path} read_mac`);
|
|
87
|
+
return [buffer, port.path];
|
|
88
|
+
}
|
|
89
|
+
catch { }
|
|
90
|
+
return [undefined, port.path];
|
|
91
|
+
}));
|
|
92
|
+
const record = parseScanResult(result);
|
|
93
|
+
const rows = Object.keys(record).map((port) => {
|
|
94
|
+
const { device, features } = record[port];
|
|
95
|
+
return [port, device, features];
|
|
96
|
+
});
|
|
97
|
+
if (rows.length === 0) {
|
|
98
|
+
yield { type: 'warning', message: 'No available devices found.' };
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
yield {
|
|
102
|
+
type: 'step:done',
|
|
103
|
+
message: 'Found the following available devices!',
|
|
104
|
+
};
|
|
105
|
+
const allRows = [['Port', 'Device', 'Features'], ...rows];
|
|
106
|
+
const colWidths = allRows[0].map((_, colIdx) => Math.max(...allRows.map((row) => String(row[colIdx]).length)));
|
|
107
|
+
for (const row of allRows) {
|
|
108
|
+
yield {
|
|
109
|
+
type: 'info',
|
|
110
|
+
message: row
|
|
111
|
+
.map((cell, idx) => String(cell).padEnd(colWidths[idx]))
|
|
112
|
+
.join(' '),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvdG9vbGJveC9zY2FuL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxVQUFVLElBQUksS0FBSyxFQUFFLE1BQU0sc0JBQXNCLENBQUE7QUFDMUQsT0FBTyxFQUFFLFVBQVUsRUFBRSxRQUFRLEVBQUUsTUFBTSxTQUFTLENBQUE7QUFDOUMsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLG9CQUFvQixDQUFBO0FBQzdDLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxZQUFZLENBQUE7QUFDdkMsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sS0FBSyxDQUFBO0FBRXhDLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxZQUFZLENBQUE7QUFDNUMsT0FBTyxFQUFFLGlCQUFpQixFQUFFLFNBQVMsRUFBRSxNQUFNLG1CQUFtQixDQUFBO0FBRWhFLE1BQU0sQ0FBQyxPQUFPLENBQUMsS0FBSyxTQUFTLENBQUMsQ0FBQyxXQUFXO0lBQ3hDLE1BQU0sU0FBUyxHQUFHLE1BQU0saUJBQWlCLEVBQUUsQ0FBQTtJQUMzQyxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ3ZCLE1BQU07WUFDSixJQUFJLEVBQUUsU0FBUztZQUNmLE9BQU8sRUFBRSxpQ0FBaUMsU0FBUyxDQUFDLEtBQUssRUFBRTtTQUM1RCxDQUFBO0lBQ0gsQ0FBQztJQUVELElBQ0UsT0FBTyxPQUFPLENBQUMsR0FBRyxDQUFDLFFBQVEsS0FBSyxRQUFRO1FBQ3hDLFVBQVUsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQztRQUNoQyxRQUFRLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsQ0FBQyxXQUFXLEVBQUUsRUFDNUMsQ0FBQztRQUNELE1BQU0sRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSx3Q0FBd0MsRUFB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
|