espcli 0.0.1 → 0.0.3
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/dist/index.js +107 -83
- package/package.json +4 -5
- package/src/core/constants.ts +0 -107
- package/src/core/emitter.ts +0 -48
- package/src/core/operations/build.ts +0 -70
- package/src/core/operations/clean.ts +0 -42
- package/src/core/operations/devices.ts +0 -11
- package/src/core/operations/flash.ts +0 -49
- package/src/core/operations/init.ts +0 -39
- package/src/core/operations/install.ts +0 -99
- package/src/core/operations/monitor.ts +0 -67
- package/src/core/services/health.ts +0 -214
- package/src/core/services/idf.ts +0 -98
- package/src/core/services/ports.ts +0 -172
- package/src/core/services/process.ts +0 -144
- package/src/core/services/shell.ts +0 -74
- package/src/core/templates/files.ts +0 -78
- package/src/core/templates/index.ts +0 -52
- package/src/core/types.ts +0 -128
- package/src/index.ts +0 -5
- package/src/serve.ts +0 -8
- package/src/server/index.ts +0 -105
- package/src/server/routes.ts +0 -175
- package/src/server/schema.ts +0 -81
- package/src/tui/commands/build.ts +0 -48
- package/src/tui/commands/clean.ts +0 -36
- package/src/tui/commands/devices.ts +0 -18
- package/src/tui/commands/doctor.ts +0 -70
- package/src/tui/commands/flash.ts +0 -61
- package/src/tui/commands/init.ts +0 -59
- package/src/tui/commands/install.ts +0 -89
- package/src/tui/commands/monitor.ts +0 -63
- package/src/tui/commands/run.ts +0 -99
- package/src/tui/display.ts +0 -96
- package/src/tui/index.ts +0 -96
- package/src/tui/logger.ts +0 -35
- package/src/tui/prompts.ts +0 -99
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "espcli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Modern CLI for ESP-IDF development — install, create, build, flash, and monitor ESP32 projects",
|
|
5
|
-
"module": "src/index.ts",
|
|
6
5
|
"type": "module",
|
|
7
6
|
"scripts": {
|
|
8
7
|
"dev": "bun run src/index.ts",
|
|
9
8
|
"serve": "bun run src/serve.ts",
|
|
10
|
-
"build": "bun build ./src/index.ts --outdir ./dist --target
|
|
9
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target node --minify",
|
|
11
10
|
"test": "bun test",
|
|
12
11
|
"typecheck": "tsc --noEmit",
|
|
13
12
|
"web": "cd web && bun run dev",
|
|
@@ -16,7 +15,7 @@
|
|
|
16
15
|
"publish": "bun run build && bun publish --access public"
|
|
17
16
|
},
|
|
18
17
|
"bin": {
|
|
19
|
-
"espcli": "./
|
|
18
|
+
"espcli": "./dist/index.js"
|
|
20
19
|
},
|
|
21
20
|
"devDependencies": {
|
|
22
21
|
"@types/bun": "latest"
|
|
@@ -24,8 +23,8 @@
|
|
|
24
23
|
"peerDependencies": {
|
|
25
24
|
"typescript": "^5.0.0"
|
|
26
25
|
},
|
|
26
|
+
"types": "dist/index.d.ts",
|
|
27
27
|
"files": [
|
|
28
|
-
"src",
|
|
29
28
|
"dist"
|
|
30
29
|
],
|
|
31
30
|
"repository": {
|
package/src/core/constants.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import type { EspTarget } from '@/core/types';
|
|
2
|
-
import { homedir } from 'os';
|
|
3
|
-
import { join } from 'path';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Supported ESP32 target chips
|
|
7
|
-
* @see https://github.com/espressif/esptool/blob/master/esptool/targets/__init__.py
|
|
8
|
-
*/
|
|
9
|
-
export const ESP_TARGETS: EspTarget[] = [
|
|
10
|
-
// Xtensa-based chips
|
|
11
|
-
{ id: 'esp32', name: 'ESP32', description: 'Original dual-core Xtensa', stable: true },
|
|
12
|
-
{ id: 'esp32s2', name: 'ESP32-S2', description: 'Single-core Xtensa with USB OTG', stable: true },
|
|
13
|
-
{ id: 'esp32s3', name: 'ESP32-S3', description: 'Dual-core Xtensa with AI acceleration', stable: true },
|
|
14
|
-
{ id: 'esp32s31', name: 'ESP32-S3 (rev1)', description: 'ESP32-S3 revision 1', stable: false },
|
|
15
|
-
|
|
16
|
-
// RISC-V based chips
|
|
17
|
-
{ id: 'esp32c2', name: 'ESP32-C2', description: 'Single-core RISC-V (cost-optimized)', stable: true },
|
|
18
|
-
{ id: 'esp32c3', name: 'ESP32-C3', description: 'Single-core RISC-V', stable: true },
|
|
19
|
-
{ id: 'esp32c5', name: 'ESP32-C5', description: 'RISC-V with WiFi 6', stable: false },
|
|
20
|
-
{ id: 'esp32c6', name: 'ESP32-C6', description: 'RISC-V with WiFi 6 & 802.15.4', stable: true },
|
|
21
|
-
{ id: 'esp32c61', name: 'ESP32-C61', description: 'ESP32-C6 variant', stable: false },
|
|
22
|
-
|
|
23
|
-
// 802.15.4/Thread/Zigbee chips
|
|
24
|
-
{ id: 'esp32h2', name: 'ESP32-H2', description: 'RISC-V with 802.15.4/Zigbee/Thread', stable: true },
|
|
25
|
-
{ id: 'esp32h21', name: 'ESP32-H21', description: 'ESP32-H2 variant', stable: false },
|
|
26
|
-
{ id: 'esp32h4', name: 'ESP32-H4', description: 'RISC-V 802.15.4', stable: false },
|
|
27
|
-
|
|
28
|
-
// High-performance chips
|
|
29
|
-
{ id: 'esp32p4', name: 'ESP32-P4', description: 'High-performance dual-core RISC-V', stable: false },
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
export const DEFAULT_IDF_PATH = join(homedir(), 'esp', 'esp-idf');
|
|
33
|
-
export const DEFAULT_ESP_PATH = join(homedir(), 'esp');
|
|
34
|
-
export const IDF_REPO_URL = 'https://github.com/espressif/esp-idf.git';
|
|
35
|
-
|
|
36
|
-
export const DEFAULT_FLASH_BAUD = 460800;
|
|
37
|
-
export const DEFAULT_MONITOR_BAUD = 115200;
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* USB Vendor/Product IDs for ESP32 development boards
|
|
41
|
-
*
|
|
42
|
-
* USB-IF Vendor IDs:
|
|
43
|
-
* - 0x303A = Espressif Systems (registered USB vendor): https://github.com/espressif/usb-pids
|
|
44
|
-
* - 0x10C4 = Silicon Labs (CP210x USB-UART bridges)
|
|
45
|
-
* - 0x1A86 = QinHeng Electronics (CH340/CH343 USB-UART bridges)
|
|
46
|
-
* - 0x0403 = FTDI (FT232 USB-UART bridges)
|
|
47
|
-
*
|
|
48
|
-
* References:
|
|
49
|
-
* - USB ID Database: http://www.linux-usb.org/usb.ids
|
|
50
|
-
* - Espressif USB PIDs: https://github.com/espressif/esptool/blob/master/esptool/loader.py
|
|
51
|
-
* (search for USB_JTAG_SERIAL_PID = 0x1001)
|
|
52
|
-
*
|
|
53
|
-
* Important: Espressif PIDs identify USB MODE, not specific chip variant:
|
|
54
|
-
* - 0x1001 = USB-JTAG/Serial mode (used by ESP32-S2, S3, C3, C6, H2 with built-in USB)
|
|
55
|
-
* - 0x1002 = USB-OTG mode (ESP32-S2, S3)
|
|
56
|
-
* To identify the actual chip, use esptool.py chip_id command.
|
|
57
|
-
*/
|
|
58
|
-
export const USB_VENDORS: Record<string, { name: string; chips: Record<string, string> }> = {
|
|
59
|
-
// Silicon Labs CP210x - common on older ESP32 dev boards
|
|
60
|
-
// @see https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers
|
|
61
|
-
'10C4': {
|
|
62
|
-
name: 'Silicon Labs',
|
|
63
|
-
chips: {
|
|
64
|
-
'EA60': 'CP210x',
|
|
65
|
-
'EA70': 'CP2105',
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
// WCH (QinHeng) CH340/CH343 - common on budget ESP32 boards
|
|
69
|
-
// @see https://www.wch-ic.com/products/CH343.html
|
|
70
|
-
'1A86': {
|
|
71
|
-
name: 'QinHeng Electronics',
|
|
72
|
-
chips: {
|
|
73
|
-
'7523': 'CH340',
|
|
74
|
-
'5523': 'CH341',
|
|
75
|
-
'55D3': 'CH343', // High-speed USB-UART, common on newer boards
|
|
76
|
-
'55D4': 'CH9102',
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
// FTDI - professional-grade USB-UART bridges
|
|
80
|
-
// @see https://ftdichip.com/products/ft232r/
|
|
81
|
-
'0403': {
|
|
82
|
-
name: 'FTDI',
|
|
83
|
-
chips: {
|
|
84
|
-
'6001': 'FT232R',
|
|
85
|
-
'6010': 'FT2232',
|
|
86
|
-
'6011': 'FT4232',
|
|
87
|
-
'6014': 'FT232H',
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
// Espressif native USB - built into ESP32-S2/S3/C3/C6/H2
|
|
91
|
-
// @see https://github.com/espressif/esptool/blob/master/esptool/loader.py#L77
|
|
92
|
-
'303A': {
|
|
93
|
-
name: 'Espressif',
|
|
94
|
-
chips: {
|
|
95
|
-
'1001': 'ESP32 (USB-JTAG)', // USB_JTAG_SERIAL_PID
|
|
96
|
-
'1002': 'ESP32 (USB-OTG)', // USB_OTG_PID
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
export const SHELL_CONFIGS: Record<string, string> = {
|
|
102
|
-
zsh: '.zshrc',
|
|
103
|
-
bash: '.bashrc',
|
|
104
|
-
fish: '.config/fish/config.fish',
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
export const VERSION = '0.0.1';
|
package/src/core/emitter.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import type { CoreEvent, EventData } from '@/core/types';
|
|
2
|
-
|
|
3
|
-
type EventHandler = (event: CoreEvent) => void;
|
|
4
|
-
|
|
5
|
-
class OperationEmitter {
|
|
6
|
-
private handlers = new Map<string, Set<EventHandler>>();
|
|
7
|
-
private globalHandlers = new Set<EventHandler>();
|
|
8
|
-
|
|
9
|
-
subscribe(operationId: string, handler: EventHandler): () => void {
|
|
10
|
-
if (!this.handlers.has(operationId)) {
|
|
11
|
-
this.handlers.set(operationId, new Set());
|
|
12
|
-
}
|
|
13
|
-
this.handlers.get(operationId)!.add(handler);
|
|
14
|
-
|
|
15
|
-
return () => {
|
|
16
|
-
this.handlers.get(operationId)?.delete(handler);
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
subscribeAll(handler: EventHandler): () => void {
|
|
21
|
-
this.globalHandlers.add(handler);
|
|
22
|
-
return () => {
|
|
23
|
-
this.globalHandlers.delete(handler);
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
emit(operationId: string, data: EventData): void {
|
|
28
|
-
const event: CoreEvent = {
|
|
29
|
-
type: data.type,
|
|
30
|
-
timestamp: Date.now(),
|
|
31
|
-
operationId,
|
|
32
|
-
data,
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
this.handlers.get(operationId)?.forEach((h) => h(event));
|
|
36
|
-
this.globalHandlers.forEach((h) => h(event));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
cleanup(operationId: string): void {
|
|
40
|
-
this.handlers.delete(operationId);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export const emitter = new OperationEmitter();
|
|
45
|
-
|
|
46
|
-
export function createOperationId(): string {
|
|
47
|
-
return `op_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
48
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import type { BuildConfig, Result } from '@/core/types';
|
|
2
|
-
import { emitter, createOperationId } from '@/core/emitter';
|
|
3
|
-
import { runWithIdf } from '@/core/services/process';
|
|
4
|
-
import { isIdfProject } from '@/core/services/idf';
|
|
5
|
-
|
|
6
|
-
export interface BuildResult {
|
|
7
|
-
success: boolean;
|
|
8
|
-
projectDir: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export async function build(
|
|
12
|
-
config: BuildConfig,
|
|
13
|
-
operationId?: string
|
|
14
|
-
): Promise<Result<BuildResult>> {
|
|
15
|
-
const opId = operationId || createOperationId();
|
|
16
|
-
const { projectDir, target, clean } = config;
|
|
17
|
-
|
|
18
|
-
const isProject = await isIdfProject(projectDir);
|
|
19
|
-
if (!isProject) {
|
|
20
|
-
const error = 'Not an ESP-IDF project directory';
|
|
21
|
-
emitter.emit(opId, { type: 'error', message: error });
|
|
22
|
-
return { ok: false, error };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (target) {
|
|
26
|
-
emitter.emit(opId, { type: 'progress', message: `Setting target to ${target}...` });
|
|
27
|
-
|
|
28
|
-
const targetResult = await runWithIdf('idf.py', ['set-target', target], {
|
|
29
|
-
cwd: projectDir,
|
|
30
|
-
operationId: opId,
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
if (!targetResult.ok || targetResult.data.exitCode !== 0) {
|
|
34
|
-
const error = 'Failed to set target';
|
|
35
|
-
emitter.emit(opId, { type: 'error', message: error });
|
|
36
|
-
return { ok: false, error };
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (clean) {
|
|
41
|
-
emitter.emit(opId, { type: 'progress', message: 'Cleaning...' });
|
|
42
|
-
|
|
43
|
-
await runWithIdf('idf.py', ['clean'], {
|
|
44
|
-
cwd: projectDir,
|
|
45
|
-
operationId: opId,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
emitter.emit(opId, { type: 'progress', message: 'Building project...' });
|
|
50
|
-
|
|
51
|
-
const buildResult = await runWithIdf('idf.py', ['build'], {
|
|
52
|
-
cwd: projectDir,
|
|
53
|
-
operationId: opId,
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
if (!buildResult.ok) {
|
|
57
|
-
emitter.emit(opId, { type: 'error', message: buildResult.error });
|
|
58
|
-
return { ok: false, error: buildResult.error };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (buildResult.data.exitCode !== 0) {
|
|
62
|
-
const error = 'Build failed';
|
|
63
|
-
emitter.emit(opId, { type: 'error', message: error });
|
|
64
|
-
return { ok: false, error };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
emitter.emit(opId, { type: 'complete', result: { success: true, projectDir } });
|
|
68
|
-
|
|
69
|
-
return { ok: true, data: { success: true, projectDir } };
|
|
70
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import type { CleanConfig, Result } from '@/core/types';
|
|
2
|
-
import { emitter, createOperationId } from '@/core/emitter';
|
|
3
|
-
import { runWithIdf } from '@/core/services/process';
|
|
4
|
-
import { isIdfProject } from '@/core/services/idf';
|
|
5
|
-
|
|
6
|
-
export async function clean(
|
|
7
|
-
config: CleanConfig,
|
|
8
|
-
operationId?: string
|
|
9
|
-
): Promise<Result<void>> {
|
|
10
|
-
const opId = operationId || createOperationId();
|
|
11
|
-
const { projectDir, full } = config;
|
|
12
|
-
|
|
13
|
-
const isProject = await isIdfProject(projectDir);
|
|
14
|
-
if (!isProject) {
|
|
15
|
-
const error = 'Not an ESP-IDF project directory';
|
|
16
|
-
emitter.emit(opId, { type: 'error', message: error });
|
|
17
|
-
return { ok: false, error };
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const command = full ? 'fullclean' : 'clean';
|
|
21
|
-
emitter.emit(opId, { type: 'progress', message: `Running ${command}...` });
|
|
22
|
-
|
|
23
|
-
const result = await runWithIdf('idf.py', [command], {
|
|
24
|
-
cwd: projectDir,
|
|
25
|
-
operationId: opId,
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
if (!result.ok) {
|
|
29
|
-
emitter.emit(opId, { type: 'error', message: result.error });
|
|
30
|
-
return { ok: false, error: result.error };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (result.data.exitCode !== 0) {
|
|
34
|
-
const error = `${command} failed`;
|
|
35
|
-
emitter.emit(opId, { type: 'error', message: error });
|
|
36
|
-
return { ok: false, error };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
emitter.emit(opId, { type: 'complete', result: null });
|
|
40
|
-
|
|
41
|
-
return { ok: true, data: undefined };
|
|
42
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { SerialDevice, Result } from '@/core/types';
|
|
2
|
-
import { listPorts } from '@/core/services/ports';
|
|
3
|
-
|
|
4
|
-
export async function listDevices(): Promise<Result<SerialDevice[]>> {
|
|
5
|
-
try {
|
|
6
|
-
const devices = await listPorts();
|
|
7
|
-
return { ok: true, data: devices };
|
|
8
|
-
} catch (err) {
|
|
9
|
-
return { ok: false, error: `Failed to list devices: ${err}` };
|
|
10
|
-
}
|
|
11
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import type { FlashConfig, Result } from '@/core/types';
|
|
2
|
-
import { emitter, createOperationId } from '@/core/emitter';
|
|
3
|
-
import { runWithIdf } from '@/core/services/process';
|
|
4
|
-
import { isIdfProject } from '@/core/services/idf';
|
|
5
|
-
import { DEFAULT_FLASH_BAUD } from '@/core/constants';
|
|
6
|
-
|
|
7
|
-
export interface FlashResult {
|
|
8
|
-
success: boolean;
|
|
9
|
-
port: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export async function flash(
|
|
13
|
-
config: FlashConfig,
|
|
14
|
-
operationId?: string
|
|
15
|
-
): Promise<Result<FlashResult>> {
|
|
16
|
-
const opId = operationId || createOperationId();
|
|
17
|
-
const { projectDir, port, baud = DEFAULT_FLASH_BAUD } = config;
|
|
18
|
-
|
|
19
|
-
const isProject = await isIdfProject(projectDir);
|
|
20
|
-
if (!isProject) {
|
|
21
|
-
const error = 'Not an ESP-IDF project directory';
|
|
22
|
-
emitter.emit(opId, { type: 'error', message: error });
|
|
23
|
-
return { ok: false, error };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
emitter.emit(opId, { type: 'progress', message: `Flashing to ${port}...` });
|
|
27
|
-
|
|
28
|
-
const args = ['-p', port, '-b', String(baud), 'flash'];
|
|
29
|
-
|
|
30
|
-
const flashResult = await runWithIdf('idf.py', args, {
|
|
31
|
-
cwd: projectDir,
|
|
32
|
-
operationId: opId,
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
if (!flashResult.ok) {
|
|
36
|
-
emitter.emit(opId, { type: 'error', message: flashResult.error });
|
|
37
|
-
return { ok: false, error: flashResult.error };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (flashResult.data.exitCode !== 0) {
|
|
41
|
-
const error = 'Flash failed';
|
|
42
|
-
emitter.emit(opId, { type: 'error', message: error });
|
|
43
|
-
return { ok: false, error };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
emitter.emit(opId, { type: 'complete', result: { success: true, port } });
|
|
47
|
-
|
|
48
|
-
return { ok: true, data: { success: true, port } };
|
|
49
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import type { InitConfig, InitResult, Result } from '@/core/types';
|
|
2
|
-
import { createProject } from '@/core/templates';
|
|
3
|
-
import { emitter, createOperationId } from '@/core/emitter';
|
|
4
|
-
import { runWithIdf } from '@/core/services/process';
|
|
5
|
-
|
|
6
|
-
export async function init(
|
|
7
|
-
config: InitConfig,
|
|
8
|
-
operationId?: string
|
|
9
|
-
): Promise<Result<InitResult>> {
|
|
10
|
-
const opId = operationId || createOperationId();
|
|
11
|
-
|
|
12
|
-
emitter.emit(opId, { type: 'progress', message: `Creating project ${config.name}...` });
|
|
13
|
-
|
|
14
|
-
const result = await createProject(config);
|
|
15
|
-
|
|
16
|
-
if (!result.ok) {
|
|
17
|
-
emitter.emit(opId, { type: 'error', message: result.error });
|
|
18
|
-
return result;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
emitter.emit(opId, { type: 'progress', message: 'Setting target...', percent: 80 });
|
|
22
|
-
|
|
23
|
-
const setTargetResult = await runWithIdf('idf.py', ['set-target', config.target], {
|
|
24
|
-
cwd: result.data.projectPath,
|
|
25
|
-
operationId: opId,
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
if (!setTargetResult.ok || setTargetResult.data.exitCode !== 0) {
|
|
29
|
-
emitter.emit(opId, {
|
|
30
|
-
type: 'log',
|
|
31
|
-
level: 'warn',
|
|
32
|
-
message: 'Failed to set target. You may need to run: idf.py set-target ' + config.target,
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
emitter.emit(opId, { type: 'complete', result: result.data });
|
|
37
|
-
|
|
38
|
-
return result;
|
|
39
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import type { InstallConfig, InstallResult, Result } from '@/core/types';
|
|
2
|
-
import { emitter, createOperationId } from '@/core/emitter';
|
|
3
|
-
import { DEFAULT_ESP_PATH, IDF_REPO_URL } from '@/core/constants';
|
|
4
|
-
import { run } from '@/core/services/process';
|
|
5
|
-
import { addToShellConfig, getExportCommand } from '@/core/services/shell';
|
|
6
|
-
import { getIdfVersion } from '@/core/services/idf';
|
|
7
|
-
import { mkdir, access } from 'fs/promises';
|
|
8
|
-
import { join } from 'path';
|
|
9
|
-
|
|
10
|
-
export async function install(
|
|
11
|
-
config: Partial<InstallConfig> = {},
|
|
12
|
-
operationId?: string
|
|
13
|
-
): Promise<Result<InstallResult>> {
|
|
14
|
-
const opId = operationId || createOperationId();
|
|
15
|
-
const espPath = config.path || DEFAULT_ESP_PATH;
|
|
16
|
-
const target = config.target || 'all';
|
|
17
|
-
const addToShell = config.addToShell ?? true;
|
|
18
|
-
const idfPath = join(espPath, 'esp-idf');
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
await access(idfPath);
|
|
22
|
-
const version = await getIdfVersion(idfPath);
|
|
23
|
-
emitter.emit(opId, { type: 'log', level: 'info', message: `ESP-IDF already installed at ${idfPath}` });
|
|
24
|
-
return {
|
|
25
|
-
ok: true,
|
|
26
|
-
data: {
|
|
27
|
-
idfPath,
|
|
28
|
-
version: version || 'unknown',
|
|
29
|
-
addedToShell: false,
|
|
30
|
-
},
|
|
31
|
-
};
|
|
32
|
-
} catch {}
|
|
33
|
-
|
|
34
|
-
emitter.emit(opId, { type: 'progress', message: 'Creating ESP directory...' });
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
await mkdir(espPath, { recursive: true });
|
|
38
|
-
} catch (err) {
|
|
39
|
-
return { ok: false, error: `Failed to create directory: ${err}` };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
emitter.emit(opId, { type: 'progress', message: 'Cloning ESP-IDF repository...', percent: 10 });
|
|
43
|
-
|
|
44
|
-
const cloneResult = await run('git', ['clone', '--recursive', IDF_REPO_URL], {
|
|
45
|
-
cwd: espPath,
|
|
46
|
-
operationId: opId,
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
if (!cloneResult.ok) {
|
|
50
|
-
return { ok: false, error: cloneResult.error };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (cloneResult.data.exitCode !== 0) {
|
|
54
|
-
return { ok: false, error: `Git clone failed with exit code ${cloneResult.data.exitCode}` };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
emitter.emit(opId, { type: 'progress', message: 'Running install script...', percent: 50 });
|
|
58
|
-
|
|
59
|
-
const installArgs = target === 'all' ? ['all'] : [target];
|
|
60
|
-
const installResult = await run('./install.sh', installArgs, {
|
|
61
|
-
cwd: idfPath,
|
|
62
|
-
operationId: opId,
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
if (!installResult.ok) {
|
|
66
|
-
return { ok: false, error: installResult.error };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (installResult.data.exitCode !== 0) {
|
|
70
|
-
return { ok: false, error: `Install script failed with exit code ${installResult.data.exitCode}` };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
let addedToShell = false;
|
|
74
|
-
|
|
75
|
-
if (addToShell) {
|
|
76
|
-
emitter.emit(opId, { type: 'progress', message: 'Configuring shell...', percent: 90 });
|
|
77
|
-
const exportCmd = getExportCommand(idfPath);
|
|
78
|
-
const shellResult = await addToShellConfig(exportCmd);
|
|
79
|
-
|
|
80
|
-
if (shellResult.ok) {
|
|
81
|
-
addedToShell = true;
|
|
82
|
-
} else {
|
|
83
|
-
emitter.emit(opId, { type: 'log', level: 'warn', message: shellResult.error });
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const version = await getIdfVersion(idfPath);
|
|
88
|
-
|
|
89
|
-
emitter.emit(opId, { type: 'complete', result: { idfPath, version, addedToShell } });
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
ok: true,
|
|
93
|
-
data: {
|
|
94
|
-
idfPath,
|
|
95
|
-
version: version || 'unknown',
|
|
96
|
-
addedToShell,
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import type { MonitorConfig, Result } from '@/core/types';
|
|
2
|
-
import { emitter, createOperationId } from '@/core/emitter';
|
|
3
|
-
import { spawnWithIdf } from '@/core/services/process';
|
|
4
|
-
import { DEFAULT_MONITOR_BAUD } from '@/core/constants';
|
|
5
|
-
import type { ResultPromise } from 'execa';
|
|
6
|
-
|
|
7
|
-
const activeMonitors = new Map<string, ResultPromise>();
|
|
8
|
-
|
|
9
|
-
export interface MonitorHandle {
|
|
10
|
-
operationId: string;
|
|
11
|
-
stop: () => void;
|
|
12
|
-
sendInput: (data: string) => void;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function startMonitor(
|
|
16
|
-
config: MonitorConfig,
|
|
17
|
-
operationId?: string
|
|
18
|
-
): Result<MonitorHandle> {
|
|
19
|
-
const opId = operationId || createOperationId();
|
|
20
|
-
const { port, baud = DEFAULT_MONITOR_BAUD, projectDir } = config;
|
|
21
|
-
|
|
22
|
-
const args = ['-p', port, '-b', String(baud), 'monitor'];
|
|
23
|
-
|
|
24
|
-
const proc = spawnWithIdf('idf.py', args, {
|
|
25
|
-
cwd: projectDir || process.cwd(),
|
|
26
|
-
operationId: opId,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
if (!proc) {
|
|
30
|
-
const error = 'ESP-IDF not found';
|
|
31
|
-
emitter.emit(opId, { type: 'error', message: error });
|
|
32
|
-
return { ok: false, error };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
activeMonitors.set(opId, proc);
|
|
36
|
-
|
|
37
|
-
proc.then(() => {
|
|
38
|
-
activeMonitors.delete(opId);
|
|
39
|
-
emitter.emit(opId, { type: 'complete', result: { stopped: true } });
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const handle: MonitorHandle = {
|
|
43
|
-
operationId: opId,
|
|
44
|
-
stop: () => {
|
|
45
|
-
proc.kill('SIGTERM');
|
|
46
|
-
activeMonitors.delete(opId);
|
|
47
|
-
},
|
|
48
|
-
sendInput: (data: string) => {
|
|
49
|
-
proc.stdin?.write(data);
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
return { ok: true, data: handle };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function stopMonitor(operationId: string): boolean {
|
|
57
|
-
const proc = activeMonitors.get(operationId);
|
|
58
|
-
if (!proc) return false;
|
|
59
|
-
|
|
60
|
-
proc.kill('SIGTERM');
|
|
61
|
-
activeMonitors.delete(operationId);
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function getActiveMonitors(): string[] {
|
|
66
|
-
return Array.from(activeMonitors.keys());
|
|
67
|
-
}
|