@typecad/framework-zephyr 1.0.0-alpha.8
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/LICENSE +21 -0
- package/README.md +100 -0
- package/dist/async/timer-polyfill.d.ts +10 -0
- package/dist/async/timer-polyfill.js +95 -0
- package/dist/chips/controllers.d.ts +21 -0
- package/dist/chips/controllers.js +66 -0
- package/dist/chips/esp32.d.ts +2 -0
- package/dist/chips/esp32.js +59 -0
- package/dist/chips/esp32s3.d.ts +2 -0
- package/dist/chips/esp32s3.js +54 -0
- package/dist/chips/index.d.ts +15 -0
- package/dist/chips/index.js +45 -0
- package/dist/chips/resolve.d.ts +9 -0
- package/dist/chips/resolve.js +121 -0
- package/dist/chips/types.d.ts +154 -0
- package/dist/chips/types.js +14 -0
- package/dist/chips/xiao-ble.d.ts +2 -0
- package/dist/chips/xiao-ble.js +61 -0
- package/dist/debug-codegen.d.ts +33 -0
- package/dist/debug-codegen.js +162 -0
- package/dist/display/gfx.d.ts +17 -0
- package/dist/display/gfx.js +171 -0
- package/dist/display/index.d.ts +20 -0
- package/dist/display/index.js +53 -0
- package/dist/display/profiles.d.ts +25 -0
- package/dist/display/profiles.js +41 -0
- package/dist/display/touch-adapter.d.ts +9 -0
- package/dist/display/touch-adapter.js +95 -0
- package/dist/display/ui-adapter.d.ts +21 -0
- package/dist/display/ui-adapter.js +534 -0
- package/dist/dt-config/kconfig.d.ts +27 -0
- package/dist/dt-config/kconfig.js +283 -0
- package/dist/dt-config/overlay.d.ts +35 -0
- package/dist/dt-config/overlay.js +246 -0
- package/dist/framework.manifest.d.ts +98 -0
- package/dist/framework.manifest.js +525 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +12 -0
- package/dist/lowering/adc.d.ts +19 -0
- package/dist/lowering/adc.js +76 -0
- package/dist/lowering/ble.d.ts +20 -0
- package/dist/lowering/ble.js +419 -0
- package/dist/lowering/board.d.ts +9 -0
- package/dist/lowering/board.js +33 -0
- package/dist/lowering/gpio.d.ts +12 -0
- package/dist/lowering/gpio.js +123 -0
- package/dist/lowering/http.d.ts +23 -0
- package/dist/lowering/http.js +482 -0
- package/dist/lowering/i2c.d.ts +15 -0
- package/dist/lowering/i2c.js +114 -0
- package/dist/lowering/index.d.ts +29 -0
- package/dist/lowering/index.js +93 -0
- package/dist/lowering/interrupts.d.ts +21 -0
- package/dist/lowering/interrupts.js +91 -0
- package/dist/lowering/mqtt.d.ts +19 -0
- package/dist/lowering/mqtt.js +366 -0
- package/dist/lowering/power.d.ts +9 -0
- package/dist/lowering/power.js +64 -0
- package/dist/lowering/preferences.d.ts +23 -0
- package/dist/lowering/preferences.js +345 -0
- package/dist/lowering/pulse.d.ts +7 -0
- package/dist/lowering/pulse.js +82 -0
- package/dist/lowering/pwm.d.ts +15 -0
- package/dist/lowering/pwm.js +59 -0
- package/dist/lowering/random.d.ts +18 -0
- package/dist/lowering/random.js +129 -0
- package/dist/lowering/spi.d.ts +17 -0
- package/dist/lowering/spi.js +113 -0
- package/dist/lowering/timing.d.ts +9 -0
- package/dist/lowering/timing.js +63 -0
- package/dist/lowering/tone.d.ts +10 -0
- package/dist/lowering/tone.js +50 -0
- package/dist/lowering/uart.d.ts +15 -0
- package/dist/lowering/uart.js +102 -0
- package/dist/lowering/util.d.ts +4 -0
- package/dist/lowering/util.js +12 -0
- package/dist/lowering/wdt.d.ts +15 -0
- package/dist/lowering/wdt.js +80 -0
- package/dist/lowering/wifi.d.ts +21 -0
- package/dist/lowering/wifi.js +447 -0
- package/dist/lowering/worker-backing.d.ts +14 -0
- package/dist/lowering/worker-backing.js +79 -0
- package/dist/lowering/worker.d.ts +6 -0
- package/dist/lowering/worker.js +14 -0
- package/dist/strategy.d.ts +168 -0
- package/dist/strategy.js +1094 -0
- package/dist/toolchain/debug-config.d.ts +82 -0
- package/dist/toolchain/debug-config.js +359 -0
- package/dist/toolchain/index.d.ts +73 -0
- package/dist/toolchain/index.js +427 -0
- package/dist/toolchain/scaffold.d.ts +15 -0
- package/dist/toolchain/scaffold.js +196 -0
- package/dist/toolchain/west-discover.d.ts +40 -0
- package/dist/toolchain/west-discover.js +203 -0
- package/dist/toolchain/west-spawn.d.ts +39 -0
- package/dist/toolchain/west-spawn.js +117 -0
- package/package.json +82 -0
- package/src/async/timer-polyfill.ts +107 -0
- package/src/chips/controllers.ts +74 -0
- package/src/chips/esp32.ts +62 -0
- package/src/chips/esp32s3.ts +57 -0
- package/src/chips/index.ts +54 -0
- package/src/chips/resolve.ts +146 -0
- package/src/chips/types.ts +163 -0
- package/src/chips/xiao-ble.ts +64 -0
- package/src/debug-codegen.ts +207 -0
- package/src/display/gfx.ts +190 -0
- package/src/display/index.ts +70 -0
- package/src/display/profiles.ts +63 -0
- package/src/display/touch-adapter.ts +100 -0
- package/src/display/ui-adapter.ts +558 -0
- package/src/dt-config/kconfig.ts +308 -0
- package/src/dt-config/overlay.ts +293 -0
- package/src/framework.manifest.ts +545 -0
- package/src/index.ts +23 -0
- package/src/lowering/adc.ts +102 -0
- package/src/lowering/ble.ts +426 -0
- package/src/lowering/board.ts +38 -0
- package/src/lowering/gpio.ts +148 -0
- package/src/lowering/http.ts +488 -0
- package/src/lowering/i2c.ts +126 -0
- package/src/lowering/index.ts +86 -0
- package/src/lowering/interrupts.ts +114 -0
- package/src/lowering/mqtt.ts +371 -0
- package/src/lowering/power.ts +70 -0
- package/src/lowering/preferences.ts +354 -0
- package/src/lowering/pulse.ts +93 -0
- package/src/lowering/pwm.ts +74 -0
- package/src/lowering/random.ts +135 -0
- package/src/lowering/spi.ts +124 -0
- package/src/lowering/timing.ts +72 -0
- package/src/lowering/tone.ts +60 -0
- package/src/lowering/uart.ts +111 -0
- package/src/lowering/util.ts +12 -0
- package/src/lowering/wdt.ts +84 -0
- package/src/lowering/wifi.ts +452 -0
- package/src/lowering/worker-backing.ts +94 -0
- package/src/lowering/worker.ts +17 -0
- package/src/strategy.ts +1265 -0
- package/src/toolchain/debug-config.ts +399 -0
- package/src/toolchain/index.ts +459 -0
- package/src/toolchain/scaffold.ts +196 -0
- package/src/toolchain/west-discover.ts +233 -0
- package/src/toolchain/west-spawn.ts +141 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// west discovery — find a usable `west` (and the Zephyr SDK / ZEPHYR_BASE)
|
|
3
|
+
//
|
|
4
|
+
// west installs into a Python venv that must be activated before `west` is on
|
|
5
|
+
// PATH. We resolve a working invocation WITHOUT requiring the user to have
|
|
6
|
+
// activated the venv, by preferring the robust `<python> -m west` form: it
|
|
7
|
+
// sidesteps shebang-launcher fragility on Windows and works with any venv
|
|
8
|
+
// once we know which Python interpreter has west installed.
|
|
9
|
+
//
|
|
10
|
+
// Discovery cascade (first usable wins):
|
|
11
|
+
// 1. `west` already on PATH (env already activated / global install).
|
|
12
|
+
// 2. $ZEPHYR_BASE venv: ${ZEPHYR_BASE}/../.venv/<python> -m west.
|
|
13
|
+
// 3. Well-known workspace layouts: ~/zephyrproject/.venv, /opt/zephyrproject/.
|
|
14
|
+
// venv, etc.
|
|
15
|
+
// 4. System pythons (`python`, `python3`, `py`) via `-m west`.
|
|
16
|
+
//
|
|
17
|
+
// Leaner than the ESP-IDF equivalent: west needs no env sourcing (no 15s
|
|
18
|
+
// export.sh) — only the right interpreter + ZEPHYR_BASE.
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
import { existsSync, realpathSync, statSync } from 'node:fs';
|
|
22
|
+
import { dirname, join } from 'node:path';
|
|
23
|
+
import { homedir } from 'node:os';
|
|
24
|
+
import { spawnSync } from 'node:child_process';
|
|
25
|
+
|
|
26
|
+
const IS_WIN = process.platform === 'win32';
|
|
27
|
+
|
|
28
|
+
/** The Python executable name inside a venv's bin/ (POSIX) or Scripts/ (Win). */
|
|
29
|
+
function venvPython(venvDir: string): string {
|
|
30
|
+
return join(venvDir, IS_WIN ? 'Scripts' : 'bin', IS_WIN ? 'python.exe' : 'python');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** True if `exe` runs `python -m west --version` successfully. */
|
|
34
|
+
function pythonRunsWest(exe: string): boolean {
|
|
35
|
+
try {
|
|
36
|
+
const r = spawnSync(exe, ['-m', 'west', '--version'], {
|
|
37
|
+
encoding: 'utf8',
|
|
38
|
+
timeout: 15_000,
|
|
39
|
+
windowsHide: true,
|
|
40
|
+
});
|
|
41
|
+
return r.status === 0;
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** True if `cmd` runs `west --version` successfully. */
|
|
48
|
+
function westOnPath(cmd: string): boolean {
|
|
49
|
+
try {
|
|
50
|
+
const r = spawnSync(cmd, ['--version'], {
|
|
51
|
+
encoding: 'utf8',
|
|
52
|
+
timeout: 15_000,
|
|
53
|
+
shell: IS_WIN,
|
|
54
|
+
windowsHide: true,
|
|
55
|
+
});
|
|
56
|
+
return r.status === 0;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A discovered, usable west installation. `mode` tells the caller how to
|
|
64
|
+
* invoke it: 'launcher' = call `westExecutable` directly; 'module' = call
|
|
65
|
+
* `pythonExecutable -m west`.
|
|
66
|
+
*/
|
|
67
|
+
export interface WestInstall {
|
|
68
|
+
mode: 'launcher' | 'module';
|
|
69
|
+
/** Absolute path to a `west` launcher (mode 'launcher') or undefined. */
|
|
70
|
+
westExecutable?: string;
|
|
71
|
+
/** Absolute path to a Python interpreter with west installed (mode 'module'). */
|
|
72
|
+
pythonExecutable?: string;
|
|
73
|
+
/** Absolute path to the Zephyr SDK root (for $ZEPHYR_BASE), if found. */
|
|
74
|
+
zephyrBase?: string;
|
|
75
|
+
/** Which discovery strategy found this install. */
|
|
76
|
+
source: 'path' | 'zephyr-base-venv' | 'well-known' | 'system-python';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** True if `dir` looks like a Zephyr SDK root: has CMakeLists.txt and the
|
|
80
|
+
* kernel header. */
|
|
81
|
+
export function isZephyrBase(dir: string): boolean {
|
|
82
|
+
if (!dir) return false;
|
|
83
|
+
return (
|
|
84
|
+
existsSync(join(dir, 'CMakeLists.txt')) &&
|
|
85
|
+
existsSync(join(dir, 'include', 'zephyr', 'kernel.h'))
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── Strategy 1: `west` on PATH ──────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
export function discoverFromPath(): WestInstall | null {
|
|
92
|
+
const which = spawnSync(IS_WIN ? 'where' : 'which', ['west'], {
|
|
93
|
+
encoding: 'utf8',
|
|
94
|
+
shell: true,
|
|
95
|
+
windowsHide: true,
|
|
96
|
+
});
|
|
97
|
+
if (which.status !== 0) return null;
|
|
98
|
+
const lines = (which.stdout ?? '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
99
|
+
for (const line of lines) {
|
|
100
|
+
if (!existsSync(line)) continue;
|
|
101
|
+
if (!westOnPath(line)) continue;
|
|
102
|
+
return {
|
|
103
|
+
mode: 'launcher',
|
|
104
|
+
westExecutable: line,
|
|
105
|
+
zephyrBase: process.env.ZEPHYR_BASE || undefined,
|
|
106
|
+
source: 'path',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Strategy 2: $ZEPHYR_BASE sibling venv ───────────────────────────────────
|
|
113
|
+
|
|
114
|
+
/** The canonical Zephyr workspace layout puts the venv beside the SDK:
|
|
115
|
+
* <workspace>/{.venv, zephyr}. So ${ZEPHYR_BASE}/../.venv is the venv. */
|
|
116
|
+
export function discoverFromZephyrBase(): WestInstall | null {
|
|
117
|
+
const zb = process.env.ZEPHYR_BASE;
|
|
118
|
+
if (!zb || !isZephyrBase(zb)) return null;
|
|
119
|
+
const workspaceDir = dirname(zb);
|
|
120
|
+
const venvDir = join(workspaceDir, '.venv');
|
|
121
|
+
const py = venvPython(venvDir);
|
|
122
|
+
if (!existsSync(py) || !pythonRunsWest(py)) return null;
|
|
123
|
+
return {
|
|
124
|
+
mode: 'module',
|
|
125
|
+
pythonExecutable: py,
|
|
126
|
+
zephyrBase: zb,
|
|
127
|
+
source: 'zephyr-base-venv',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ── Strategy 3: well-known workspace layouts ───────────────────────────────
|
|
132
|
+
|
|
133
|
+
/** Candidate Zephyr workspace directories. Each may contain both `.venv/`
|
|
134
|
+
* and `zephyr/` (the SDK). Exported for test injection. */
|
|
135
|
+
export function wellKnownWorkspaces(): string[] {
|
|
136
|
+
const home = homedir();
|
|
137
|
+
if (IS_WIN) {
|
|
138
|
+
return [
|
|
139
|
+
join(home, 'zephyrproject'),
|
|
140
|
+
join(home, 'zephyr'),
|
|
141
|
+
'C:\\zephyrproject',
|
|
142
|
+
'C:\\zephyr',
|
|
143
|
+
];
|
|
144
|
+
}
|
|
145
|
+
return [
|
|
146
|
+
join(home, 'zephyrproject'),
|
|
147
|
+
join(home, 'zephyr'),
|
|
148
|
+
'/opt/zephyrproject',
|
|
149
|
+
'/opt/zephyr',
|
|
150
|
+
];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function discoverFromWellKnown(
|
|
154
|
+
workspaces: string[] = wellKnownWorkspaces(),
|
|
155
|
+
): WestInstall | null {
|
|
156
|
+
for (const ws of workspaces) {
|
|
157
|
+
const venvDir = join(ws, '.venv');
|
|
158
|
+
const py = venvPython(venvDir);
|
|
159
|
+
if (!existsSync(py) || !pythonRunsWest(py)) continue;
|
|
160
|
+
// Resolve ZEPHYR_BASE if the SDK sits beside the venv.
|
|
161
|
+
const zb = join(ws, 'zephyr');
|
|
162
|
+
return {
|
|
163
|
+
mode: 'module',
|
|
164
|
+
pythonExecutable: py,
|
|
165
|
+
zephyrBase: isZephyrBase(zb) ? zb : undefined,
|
|
166
|
+
source: 'well-known',
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── Strategy 4: system pythons via `-m west` ────────────────────────────────
|
|
173
|
+
|
|
174
|
+
/** Candidate system Python interpreters to probe with `-m west`. */
|
|
175
|
+
export function systemPythons(): string[] {
|
|
176
|
+
if (IS_WIN) return ['py', 'python', 'python3'];
|
|
177
|
+
return ['python3', 'python'];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function discoverFromSystemPython(
|
|
181
|
+
pythons: string[] = systemPythons(),
|
|
182
|
+
): WestInstall | null {
|
|
183
|
+
for (const py of pythons) {
|
|
184
|
+
if (!pythonRunsWest(py)) continue;
|
|
185
|
+
return {
|
|
186
|
+
mode: 'module',
|
|
187
|
+
pythonExecutable: py,
|
|
188
|
+
zephyrBase: process.env.ZEPHYR_BASE || undefined,
|
|
189
|
+
source: 'system-python',
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ── Top-level cascade ────────────────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
let cachedDiscover: WestInstall | null | undefined;
|
|
198
|
+
|
|
199
|
+
/** Clear the process-local discovery cache (for tests). */
|
|
200
|
+
export function resetWestDiscoveryCache(): void {
|
|
201
|
+
cachedDiscover = undefined;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Try each discovery strategy in order. The first usable install wins.
|
|
206
|
+
* Result is memoized for the process lifetime (west installs don't move).
|
|
207
|
+
*
|
|
208
|
+
* Order: PATH → $ZEPHYR_BASE venv → well-known workspaces → system pythons.
|
|
209
|
+
* Returns null when no usable west install is found.
|
|
210
|
+
*/
|
|
211
|
+
export function discoverWest(): WestInstall | null {
|
|
212
|
+
if (cachedDiscover !== undefined) return cachedDiscover;
|
|
213
|
+
const strategies: Array<() => WestInstall | null> = [
|
|
214
|
+
discoverFromPath,
|
|
215
|
+
discoverFromZephyrBase,
|
|
216
|
+
discoverFromWellKnown,
|
|
217
|
+
discoverFromSystemPython,
|
|
218
|
+
];
|
|
219
|
+
for (const strat of strategies) {
|
|
220
|
+
let install: WestInstall | null = null;
|
|
221
|
+
try {
|
|
222
|
+
install = strat();
|
|
223
|
+
} catch {
|
|
224
|
+
install = null;
|
|
225
|
+
}
|
|
226
|
+
if (install) {
|
|
227
|
+
cachedDiscover = install;
|
|
228
|
+
return install;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
cachedDiscover = null;
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// west spawn helper — build a spawnSync invocation for `west <args>`
|
|
3
|
+
//
|
|
4
|
+
// Mirrors framework-esp32's idfSpawn(): given the discovered WestInstall,
|
|
5
|
+
// produce {command, args, env} that the toolchain destructures into spawnSync.
|
|
6
|
+
//
|
|
7
|
+
// Two modes:
|
|
8
|
+
// - 'launcher': spawn the `west` launcher directly (mode 'launcher').
|
|
9
|
+
// - 'module' : spawn `<python> -m west ...` (mode 'module'). This is the
|
|
10
|
+
// robust cross-platform form — it activates the venv's
|
|
11
|
+
// interpreter for the one process without sourcing anything.
|
|
12
|
+
//
|
|
13
|
+
// ZEPHYR_BASE is injected into the spawn env when a SDK root was discovered
|
|
14
|
+
// (find_package(Zephyr) needs it). west's prj.conf/CMakeLists are found via
|
|
15
|
+
// the project dir regardless.
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
import type { SpawnSyncOptions } from 'node:child_process';
|
|
19
|
+
import { dirname } from 'node:path';
|
|
20
|
+
import { type WestInstall, discoverWest } from './west-discover.js';
|
|
21
|
+
|
|
22
|
+
export interface WestInvocation {
|
|
23
|
+
/** The command to spawnSync: a `west` launcher or a Python interpreter. */
|
|
24
|
+
command: string;
|
|
25
|
+
/** Args for the command (includes `-m west` when in module mode). */
|
|
26
|
+
args: string[];
|
|
27
|
+
/** Base spawn options with cwd/timeout/encoding/stdio, plus the env carrying
|
|
28
|
+
* ZEPHYR_BASE when discovered. */
|
|
29
|
+
options: SpawnSyncOptions;
|
|
30
|
+
/** The install the invocation was built from, for surfacing to the user. */
|
|
31
|
+
install: WestInstall;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The Scripts/ (Windows) or bin/ (POSIX) directory of the venv the discovered
|
|
36
|
+
* west runs under. `pythonExecutable` lives in that directory, so it is its
|
|
37
|
+
* dirname. Returns undefined for launcher-mode installs where no venv is known.
|
|
38
|
+
*/
|
|
39
|
+
function venvBinDir(install: WestInstall): string | undefined {
|
|
40
|
+
return install.pythonExecutable ? dirname(install.pythonExecutable) : undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The env to pass to the west spawn: the process env, with ZEPHYR_BASE injected
|
|
45
|
+
* when a SDK root was discovered, AND the venv's bin/Scripts dir prepended to
|
|
46
|
+
* PATH when west was found via a venv Python.
|
|
47
|
+
*
|
|
48
|
+
* The PATH prepend matters: `west flash` shells out to bare runner tools
|
|
49
|
+
* (`esptool`, `openocd`, `nrfjprog`, …) via check_call, so they resolve from
|
|
50
|
+
* PATH. Without the prepend, the user's PATH may surface a *different* tool
|
|
51
|
+
* ahead of the venv's — e.g. an older esptool whose argument spelling is
|
|
52
|
+
* incompatible with the runner. Putting the venv's bin first makes west's
|
|
53
|
+
* delegated subprocesses resolve to the same versions west itself runs under.
|
|
54
|
+
*/
|
|
55
|
+
export function buildEnv(install: WestInstall): NodeJS.ProcessEnv {
|
|
56
|
+
const env = { ...process.env };
|
|
57
|
+
if (install.zephyrBase && !env.ZEPHYR_BASE) {
|
|
58
|
+
env.ZEPHYR_BASE = install.zephyrBase;
|
|
59
|
+
}
|
|
60
|
+
const bin = venvBinDir(install);
|
|
61
|
+
if (bin) {
|
|
62
|
+
const sep = process.platform === 'win32' ? ';' : ':';
|
|
63
|
+
// On Windows the PATH environment variable may be cased as `Path` (the
|
|
64
|
+
// registry-native form, the only one populated when node is launched from
|
|
65
|
+
// PowerShell/cmd) or `PATH` (POSIX form, set by Git Bash). Writing only one
|
|
66
|
+
// casing can leave the other stale/empty, which under PowerShell would drop
|
|
67
|
+
// the user's real PATH (cmake, ninja, …) — breaking `west` configure. Read
|
|
68
|
+
// whichever casing is populated and write that same casing back, preserving
|
|
69
|
+
// the full existing value with the venv dir prepended.
|
|
70
|
+
const existing = env.Path ?? env.PATH ?? '';
|
|
71
|
+
const updated = bin + sep + existing;
|
|
72
|
+
if (env.Path !== undefined || (env.PATH === undefined && process.platform === 'win32')) {
|
|
73
|
+
env.Path = updated;
|
|
74
|
+
} else {
|
|
75
|
+
env.PATH = updated;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return env;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Build a spawn invocation that runs `west <westArgs>`.
|
|
83
|
+
*
|
|
84
|
+
* `baseOptions` carries the cwd/timeout/stdio settings the caller wants;
|
|
85
|
+
* westSpawn preserves them and injects the discovery env. The returned object
|
|
86
|
+
* destructures directly into spawnSync:
|
|
87
|
+
*
|
|
88
|
+
* const inv = westSpawn(['build', '-b', 'xiao_ble', projectRoot], { cwd, timeout });
|
|
89
|
+
* spawnSync(inv.command, inv.args, inv.options);
|
|
90
|
+
*
|
|
91
|
+
* Throws a clear, actionable error when no usable west install is discovered.
|
|
92
|
+
*/
|
|
93
|
+
export function westSpawn(
|
|
94
|
+
westArgs: string[],
|
|
95
|
+
baseOptions: SpawnSyncOptions,
|
|
96
|
+
): WestInvocation {
|
|
97
|
+
const install = discoverWest();
|
|
98
|
+
if (!install) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
[
|
|
101
|
+
'west (the Zephyr build tool) was not found.',
|
|
102
|
+
'',
|
|
103
|
+
'cuttlefish looked for it on PATH, in $ZEPHYR_BASE/.venv, in common',
|
|
104
|
+
'Zephyr workspace dirs (~/zephyrproject/.venv), and as a system',
|
|
105
|
+
"Python module (`python -m west`). To fix:",
|
|
106
|
+
'',
|
|
107
|
+
' • Run the typeCAD Zephyr installer (one command, any OS — needs Node ≥18):',
|
|
108
|
+
' node packages/zephyr-installer/install.mjs',
|
|
109
|
+
' then `micromamba activate zephyr` and retry.',
|
|
110
|
+
' • Or activate an existing Zephyr venv in this shell, or',
|
|
111
|
+
' • Or set ZEPHYR_BASE to your Zephyr SDK root (the venv at $ZEPHYR_BASE/../.venv is then used), or',
|
|
112
|
+
' • Or install west into a discoverable Python: pip install west',
|
|
113
|
+
'',
|
|
114
|
+
'See https://docs.zephyrproject.org/latest/develop/getting_started/index.html',
|
|
115
|
+
].join('\n'),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const env = buildEnv(install);
|
|
120
|
+
// Strip `shell` if present — we pass absolute paths / known commands, and
|
|
121
|
+
// an explicit shell changes arg-quoting semantics on Windows.
|
|
122
|
+
const { shell: _drop, ...optsWithoutShell } = baseOptions as any;
|
|
123
|
+
|
|
124
|
+
if (install.mode === 'launcher' && install.westExecutable) {
|
|
125
|
+
return {
|
|
126
|
+
command: install.westExecutable,
|
|
127
|
+
args: westArgs,
|
|
128
|
+
options: { ...optsWithoutShell, env },
|
|
129
|
+
install,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// module mode: <python> -m west <args>
|
|
134
|
+
const py = install.pythonExecutable!;
|
|
135
|
+
return {
|
|
136
|
+
command: py,
|
|
137
|
+
args: ['-m', 'west', ...westArgs],
|
|
138
|
+
options: { ...optsWithoutShell, env },
|
|
139
|
+
install,
|
|
140
|
+
};
|
|
141
|
+
}
|