@typecad/framework-zephyr 1.0.0-alpha.13 → 1.0.0-alpha.15
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/README.md +22 -35
- package/dist/as-built.d.ts +33 -0
- package/dist/as-built.js +188 -0
- package/dist/async/timer-polyfill.js +1 -1
- package/dist/boardgen.d.ts +128 -0
- package/dist/boardgen.js +1366 -0
- package/dist/chips/board-overrides.d.ts +7 -0
- package/dist/chips/board-overrides.js +11 -0
- package/dist/chips/controllers.d.ts +28 -8
- package/dist/chips/controllers.js +49 -12
- package/dist/chips/esp32s3.js +49 -0
- package/dist/chips/index.d.ts +6 -12
- package/dist/chips/index.js +18 -34
- package/dist/chips/resolve.d.ts +16 -2
- package/dist/chips/resolve.js +298 -15
- package/dist/chips/soc/.d.ts +2 -0
- package/dist/chips/soc/.js +129 -0
- package/dist/chips/soc/esp32.d.ts +2 -0
- package/dist/chips/soc/esp32.js +120 -0
- package/dist/chips/soc/esp32c3.d.ts +2 -0
- package/dist/chips/soc/esp32c3.js +90 -0
- package/dist/chips/soc/esp32c6.d.ts +2 -0
- package/dist/chips/soc/esp32c6.js +109 -0
- package/dist/chips/soc/esp32s3.d.ts +2 -0
- package/dist/chips/soc/esp32s3.js +189 -0
- package/dist/chips/soc/index.d.ts +2 -0
- package/dist/chips/soc/index.js +23 -0
- package/dist/chips/soc/nrf52840.d.ts +2 -0
- package/dist/chips/soc/nrf52840.js +130 -0
- package/dist/chips/soc/rp2040.d.ts +2 -0
- package/dist/chips/soc/rp2040.js +141 -0
- package/dist/chips/soc/rp2350a.d.ts +2 -0
- package/dist/chips/soc/rp2350a.js +145 -0
- package/dist/chips/soc/samd21g18a.d.ts +2 -0
- package/dist/chips/soc/samd21g18a.js +143 -0
- package/dist/chips/soc/stm32f411xe.d.ts +2 -0
- package/dist/chips/soc/stm32f411xe.js +251 -0
- package/dist/chips/types.d.ts +382 -12
- package/dist/chips/xiao-ble.js +33 -0
- package/dist/display/profiles.js +1 -1
- package/dist/doctor.js +38 -4
- package/dist/dt-config/custom-board.d.ts +23 -0
- package/dist/dt-config/custom-board.js +227 -0
- package/dist/dt-config/kconfig.d.ts +83 -1
- package/dist/dt-config/kconfig.js +116 -27
- package/dist/dt-config/overlay.js +623 -18
- package/dist/framework.manifest.d.ts +20 -30
- package/dist/framework.manifest.js +124 -178
- package/dist/index.d.ts +4 -1
- package/dist/index.js +8 -2
- package/dist/lowering/adc.d.ts +26 -5
- package/dist/lowering/adc.js +137 -31
- package/dist/lowering/ble.js +31 -28
- package/dist/lowering/dac.js +9 -9
- package/dist/lowering/fs.js +18 -10
- package/dist/lowering/gpio.d.ts +2 -0
- package/dist/lowering/gpio.js +90 -10
- package/dist/lowering/http.js +39 -14
- package/dist/lowering/hwtimer.d.ts +4 -0
- package/dist/lowering/hwtimer.js +35 -10
- package/dist/lowering/i2c.js +46 -54
- package/dist/lowering/index.d.ts +5 -5
- package/dist/lowering/index.js +15 -16
- package/dist/lowering/interrupts.d.ts +22 -5
- package/dist/lowering/interrupts.js +104 -22
- package/dist/lowering/mqtt.js +12 -6
- package/dist/lowering/power.js +0 -4
- package/dist/lowering/preferences.js +49 -77
- package/dist/lowering/pulse.js +5 -36
- package/dist/lowering/pwm.d.ts +32 -3
- package/dist/lowering/pwm.js +105 -17
- package/dist/lowering/random.js +3 -3
- package/dist/lowering/sensor.d.ts +37 -0
- package/dist/lowering/sensor.js +119 -0
- package/dist/lowering/spi.d.ts +12 -0
- package/dist/lowering/spi.js +65 -40
- package/dist/lowering/thread.d.ts +14 -0
- package/dist/lowering/thread.js +55 -0
- package/dist/lowering/timing.js +20 -46
- package/dist/lowering/tone.js +18 -5
- package/dist/lowering/uart.d.ts +13 -0
- package/dist/lowering/uart.js +72 -51
- package/dist/lowering/usb.d.ts +34 -0
- package/dist/lowering/usb.js +206 -0
- package/dist/lowering/wdt.d.ts +1 -1
- package/dist/lowering/wdt.js +17 -7
- package/dist/lowering/wifi.d.ts +3 -2
- package/dist/lowering/wifi.js +120 -111
- package/dist/sdk/board-catalog-sync.d.ts +16 -0
- package/dist/sdk/board-catalog-sync.js +41 -0
- package/dist/sdk/board-data.generated.d.ts +2 -0
- package/dist/sdk/board-data.generated.js +4 -0
- package/dist/sdk/catalog-walker.d.ts +90 -0
- package/dist/sdk/catalog-walker.js +682 -0
- package/dist/sdk/dts-reader.d.ts +83 -0
- package/dist/sdk/dts-reader.js +596 -0
- package/dist/strategy.d.ts +136 -17
- package/dist/strategy.js +881 -212
- package/dist/toolchain/bossac-touch.d.ts +18 -0
- package/dist/toolchain/bossac-touch.js +88 -0
- package/dist/toolchain/compat.d.ts +2 -2
- package/dist/toolchain/compat.js +8 -3
- package/dist/toolchain/debug-config.d.ts +21 -26
- package/dist/toolchain/debug-config.js +212 -72
- package/dist/toolchain/env-check.d.ts +4 -4
- package/dist/toolchain/env-check.js +5 -5
- package/dist/toolchain/index.d.ts +66 -1
- package/dist/toolchain/index.js +713 -58
- package/dist/toolchain/scaffold.d.ts +9 -0
- package/dist/toolchain/scaffold.js +84 -7
- package/dist/toolchain/west-discover.d.ts +8 -1
- package/dist/toolchain/west-discover.js +19 -6
- package/dist/toolchain/west-spawn.js +17 -5
- package/installer/CHANGELOG.md +473 -0
- package/installer/README.md +328 -0
- package/installer/environment.yml +46 -0
- package/installer/etc/conda/activate.d/zephyr.bat +7 -0
- package/installer/etc/conda/activate.d/zephyr.ps1 +12 -0
- package/installer/etc/conda/activate.d/zephyr.sh +20 -0
- package/installer/etc/conda/deactivate.d/zephyr.bat +6 -0
- package/installer/etc/conda/deactivate.d/zephyr.ps1 +6 -0
- package/installer/etc/conda/deactivate.d/zephyr.sh +4 -0
- package/installer/install.mjs +580 -0
- package/installer/install.ps1 +619 -0
- package/installer/install.sh +310 -0
- package/installer/lib/detect-platform.sh +62 -0
- package/installer/lib/fetch-sdk.sh +304 -0
- package/installer/lib/init-workspace.sh +153 -0
- package/installer/lib/write-activation.sh +76 -0
- package/installer/patches/tfm-c-include-separators.patch +19 -0
- package/installer/patches/zephyr-tfm-nordic-zephyr-include.patch +24 -0
- package/installer/templates/project/.typecad/activate-zephyr.ps1 +31 -0
- package/installer/templates/project/.typecad/activate-zephyr.sh +31 -0
- package/installer/templates/project/.vscode/settings.json +30 -0
- package/installer/templates/project/README.md +58 -0
- package/installer/versions.env +110 -0
- package/package.json +16 -16
- package/src/as-built.ts +206 -0
- package/src/boardgen.ts +1428 -0
- package/src/chips/controllers.ts +61 -12
- package/src/chips/index.ts +35 -54
- package/src/chips/resolve.ts +316 -14
- package/src/chips/types.ts +388 -13
- package/src/display/profiles.ts +1 -1
- package/src/doctor.ts +110 -77
- package/src/dt-config/custom-board.ts +325 -0
- package/src/dt-config/kconfig.ts +177 -19
- package/src/dt-config/overlay.ts +1058 -450
- package/src/framework.manifest.ts +125 -178
- package/src/index.ts +57 -41
- package/src/lowering/adc.ts +222 -102
- package/src/lowering/ble.ts +32 -28
- package/src/lowering/dac.ts +82 -82
- package/src/lowering/fs.ts +18 -10
- package/src/lowering/gpio.ts +242 -152
- package/src/lowering/http.ts +512 -488
- package/src/lowering/hwtimer.ts +139 -101
- package/src/lowering/i2c.ts +116 -126
- package/src/lowering/index.ts +91 -93
- package/src/lowering/interrupts.ts +113 -21
- package/src/lowering/mqtt.ts +12 -6
- package/src/lowering/preferences.ts +327 -354
- package/src/lowering/pwm.ts +167 -74
- package/src/lowering/random.ts +3 -3
- package/src/lowering/sensor.ts +155 -0
- package/src/lowering/spi.ts +164 -124
- package/src/lowering/thread.ts +63 -0
- package/src/lowering/timing.ts +46 -72
- package/src/lowering/uart.ts +73 -51
- package/src/lowering/usb.ts +221 -0
- package/src/lowering/wdt.ts +95 -84
- package/src/lowering/wifi.ts +120 -111
- package/src/sdk/board-catalog-sync.ts +62 -0
- package/src/strategy.ts +888 -229
- package/src/toolchain/bossac-touch.ts +112 -0
- package/src/toolchain/compat.ts +8 -3
- package/src/toolchain/debug-config.ts +666 -522
- package/src/toolchain/env-check.ts +9 -9
- package/src/toolchain/index.ts +765 -44
- package/src/toolchain/scaffold.ts +81 -7
- package/src/toolchain/west-discover.ts +334 -316
- package/src/toolchain/west-spawn.ts +17 -5
- package/src/async/timer-polyfill.ts +0 -107
- package/src/chips/esp32.ts +0 -74
- package/src/chips/esp32s3.ts +0 -57
- package/src/chips/xiao-ble.ts +0 -70
- package/src/lowering/power.ts +0 -70
- package/src/lowering/pulse.ts +0 -93
- package/src/lowering/tone.ts +0 -60
- package/src/lowering/worker-backing.ts +0 -94
- package/src/lowering/worker.ts +0 -17
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// typeCAD Zephyr installer — cross-platform entry point.
|
|
4
|
+
//
|
|
5
|
+
// Run it:
|
|
6
|
+
// npx --package @typecad/framework-zephyr zephyr-installer # interactive (prints a plan, waits for Enter)
|
|
7
|
+
// npx --package @typecad/framework-zephyr zephyr-installer --yes # non-interactive (skip the prompt; for CI)
|
|
8
|
+
// node install.mjs --dry-run # print the resolved plan and exit
|
|
9
|
+
//
|
|
10
|
+
// It detects the host, prints a summary of what it will do, waits for Enter
|
|
11
|
+
// (unless --yes / --dry-run / non-TTY), then delegates to the native installer:
|
|
12
|
+
//
|
|
13
|
+
// POSIX → bash install.sh (args forwarded verbatim)
|
|
14
|
+
// Windows → pwsh install.ps1 (args translated to PowerShell param names;
|
|
15
|
+
// falls back to Windows PowerShell 5.1 if pwsh is absent)
|
|
16
|
+
//
|
|
17
|
+
// The OS-native scripts remain usable directly (no confirmation gate) for
|
|
18
|
+
// power users. translateToPwsh + buildSummary are exported for unit tests; the
|
|
19
|
+
// dispatch + prompt only run when this file is the node entry point.
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
import { spawn } from 'node:child_process';
|
|
22
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
import { existsSync, readdirSync, readFileSync, realpathSync, rmSync, statSync } from 'node:fs';
|
|
25
|
+
import { homedir } from 'node:os';
|
|
26
|
+
import * as readline from 'node:readline/promises';
|
|
27
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
28
|
+
|
|
29
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
const isWin = process.platform === 'win32';
|
|
31
|
+
|
|
32
|
+
// Translate the user-facing POSIX-style flags into the PowerShell param names
|
|
33
|
+
// install.ps1 declares. Keeps the documented flag vocabulary identical across
|
|
34
|
+
// platforms; the OS-native scripts remain idiomatically written.
|
|
35
|
+
export function translateToPwsh(args) {
|
|
36
|
+
const out = [];
|
|
37
|
+
for (let i = 0; i < args.length; i++) {
|
|
38
|
+
switch (args[i]) {
|
|
39
|
+
case '--dry-run': out.push('-DryRun'); break;
|
|
40
|
+
case '--no-sdk': out.push('-NoSdk'); break;
|
|
41
|
+
case '--no-workspace': out.push('-NoWorkspace'); break;
|
|
42
|
+
case '--modify': out.push('-Modify'); break;
|
|
43
|
+
case '--prune': out.push('-Prune'); break;
|
|
44
|
+
case '--env-name': out.push('-EnvName', args[++i]); break;
|
|
45
|
+
case '--sdk-version': out.push('-SdkVersion', args[++i]); break;
|
|
46
|
+
case '--platforms': out.push('-Platforms', args[++i]); break;
|
|
47
|
+
default: out.push(args[i]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// --- confirmation-gate helpers ---------------------------------------------
|
|
54
|
+
|
|
55
|
+
// Parse versions.env (KEY=value, # comments) into an object. Strips optional
|
|
56
|
+
// double quotes around values (needed for multi-word values like PLATFORM_esp32).
|
|
57
|
+
export function loadVersionsEnv(dir = here) {
|
|
58
|
+
const v = {};
|
|
59
|
+
const text = readFileSync(join(dir, 'versions.env'), 'utf8');
|
|
60
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
61
|
+
const line = raw.trim();
|
|
62
|
+
if (!line || line.startsWith('#')) continue;
|
|
63
|
+
const idx = line.indexOf('=');
|
|
64
|
+
if (idx > 0) {
|
|
65
|
+
let val = line.slice(idx + 1).trim();
|
|
66
|
+
if (val.startsWith('"') && val.endsWith('"') && val.length >= 2) val = val.slice(1, -1);
|
|
67
|
+
v[line.slice(0, idx).trim()] = val;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return v;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Map node's platform/arch to the conda subdir + Zephyr SDK bundle tokens that
|
|
74
|
+
// the native installer will use. Mirrors lib/detect-platform.sh.
|
|
75
|
+
export function detectPlatform() {
|
|
76
|
+
const plat = process.platform;
|
|
77
|
+
const arm = process.arch === 'arm64';
|
|
78
|
+
if (plat === 'win32') return { mamba: 'win-64', sdk: 'windows-x86_64', ext: '7z' };
|
|
79
|
+
if (plat === 'darwin') return arm
|
|
80
|
+
? { mamba: 'osx-arm64', sdk: 'macos-aarch64', ext: 'tar.xz' }
|
|
81
|
+
: { mamba: 'osx-64', sdk: 'macos-x86_64', ext: 'tar.xz' };
|
|
82
|
+
// linux
|
|
83
|
+
return arm
|
|
84
|
+
? { mamba: 'linux-aarch64', sdk: 'linux-aarch64', ext: 'tar.xz' }
|
|
85
|
+
: { mamba: 'linux-64', sdk: 'linux-x86_64', ext: 'tar.xz' };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Pure: build the pre-install summary string. Exported so it's unit-testable
|
|
89
|
+
// without running the installer. `platforms` is the selection string ('all' or
|
|
90
|
+
// comma-separated group ids) used to size the SDK download line.
|
|
91
|
+
export function buildSummary(v, p, envName, platforms = 'all') {
|
|
92
|
+
const home = homedir();
|
|
93
|
+
const mambaRoot = process.env.MAMBA_ROOT_PREFIX || join(home, 'micromamba');
|
|
94
|
+
const workspace = process.env.WORKSPACE_DIR || join(home, 'zephyrproject');
|
|
95
|
+
const sdkVer = v.ZEPHYR_SDK_VERSION || '<pinned>';
|
|
96
|
+
const rev = v.ZEPHYR_MANIFEST_REV || '<pinned>';
|
|
97
|
+
// 1.0.x bundles carry a flavor suffix (_gnu); 0.17.x "full" bundles don't.
|
|
98
|
+
const suffix = v.ZEPHYR_SDK_BUNDLE_SUFFIX || '';
|
|
99
|
+
const bundle = `zephyr-sdk-${sdkVer}_${p.sdk}${suffix}.${p.ext}`;
|
|
100
|
+
const sdkLine = platforms === 'all'
|
|
101
|
+
? ` 3. Fetch + extract the Zephyr SDK ${sdkVer} — ${bundle} (~1.5 GB download, ~11 GB extracted).`
|
|
102
|
+
: ` 3. Fetch + extract the Zephyr SDK ${sdkVer} (minimal + selected toolchains — platforms: ${platforms}).`;
|
|
103
|
+
return [
|
|
104
|
+
'',
|
|
105
|
+
'typeCAD Zephyr installer',
|
|
106
|
+
'========================',
|
|
107
|
+
'',
|
|
108
|
+
'This sets up a complete Zephyr RTOS build environment via micromamba — no',
|
|
109
|
+
'preinstalled conda, Python, or toolchain required. It will:',
|
|
110
|
+
'',
|
|
111
|
+
` 1. Download the micromamba static binary (${p.mamba}).`,
|
|
112
|
+
` 2. Create the '${envName}' conda env (west, cmake, ninja, gperf, ...).`,
|
|
113
|
+
sdkLine,
|
|
114
|
+
` 4. Run 'west init' (--mr ${rev}) + 'west update' for a vanilla Zephyr workspace.`,
|
|
115
|
+
'',
|
|
116
|
+
'Locations (defaults; override via MAMBA_ROOT_PREFIX / WORKSPACE_DIR / SDK_INSTALL_PARENT):',
|
|
117
|
+
` micromamba root : ${mambaRoot}`,
|
|
118
|
+
` conda env : ${join(mambaRoot, 'envs', envName)}`,
|
|
119
|
+
` Zephyr SDK : ${join(mambaRoot, 'zephyr-sdk', `zephyr-sdk-${sdkVer}`)}`,
|
|
120
|
+
` west workspace : ${workspace} (ZEPHYR_BASE = ${join(workspace, 'zephyr')})`,
|
|
121
|
+
'',
|
|
122
|
+
'No sudo, no Zephyr SDK setup.sh — the SDK is used in place via ZEPHYR_SDK_INSTALL_DIR.',
|
|
123
|
+
'',
|
|
124
|
+
].join('\n');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Read --env-name from argv (default 'zephyr') so the summary matches what the
|
|
128
|
+
// native installer will actually name the env.
|
|
129
|
+
function envNameFromArgs(args) {
|
|
130
|
+
const i = args.indexOf('--env-name');
|
|
131
|
+
return i >= 0 && i + 1 < args.length ? args[i + 1] : 'zephyr';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// --- platform checklist ------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
// The platform groups from versions.env (PLATFORM_<id>, PLATFORM_<id>_LABEL,
|
|
137
|
+
// PLATFORM_<id>_SIZE). Returns [{id, label, size, toolchains}].
|
|
138
|
+
export function platformCatalog(v = loadVersionsEnv()) {
|
|
139
|
+
const groups = [];
|
|
140
|
+
for (const key of Object.keys(v)) {
|
|
141
|
+
const m = key.match(/^PLATFORM_([a-z0-9]+)$/);
|
|
142
|
+
if (!m) continue;
|
|
143
|
+
const id = m[1];
|
|
144
|
+
groups.push({
|
|
145
|
+
id,
|
|
146
|
+
label: v[`PLATFORM_${id}_LABEL`] || id,
|
|
147
|
+
size: v[`PLATFORM_${id}_SIZE`] || '',
|
|
148
|
+
toolchains: (v[key] || '').split(/\s+/).filter(Boolean),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return groups;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Parse the user's checklist answer ('1 2', '1,2', 'all', 'arm esp32') into a
|
|
155
|
+
// normalized comma-separated group-id string ('arm,esp32') or 'all'.
|
|
156
|
+
// Numbers index into catalog (1-based); names must match group ids.
|
|
157
|
+
export function parsePlatformSelection(answer, catalog = platformCatalog()) {
|
|
158
|
+
const trimmed = (answer || '').trim().toLowerCase();
|
|
159
|
+
if (!trimmed || trimmed === 'all' || trimmed === 'a') return 'all';
|
|
160
|
+
const picked = new Set();
|
|
161
|
+
for (const tok of trimmed.split(/[\s,]+/).filter(Boolean)) {
|
|
162
|
+
if (/^\d+$/.test(tok)) {
|
|
163
|
+
const idx = Number(tok) - 1;
|
|
164
|
+
if (idx >= 0 && idx < catalog.length) picked.add(catalog[idx].id);
|
|
165
|
+
else throw new Error(`invalid platform number: ${tok} (choose 1-${catalog.length} or 'all')`);
|
|
166
|
+
} else {
|
|
167
|
+
const grp = catalog.find((g) => g.id === tok);
|
|
168
|
+
if (!grp) throw new Error(`unknown platform: ${tok} (choose a number, a group id, or 'all')`);
|
|
169
|
+
picked.add(grp.id);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (picked.size === 0) return 'all';
|
|
173
|
+
return [...picked].join(',');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Render the checklist text. `installedToolchains` (array of toolchain target
|
|
177
|
+
// dir names found under the SDK root) marks groups already installed.
|
|
178
|
+
// 'All' is listed FIRST and is the default (Enter accepts it) — the recommended
|
|
179
|
+
// path is to install everything so any board in the pack just builds; the
|
|
180
|
+
// per-group entries are the space-saving opt-in.
|
|
181
|
+
export function buildChecklist(catalog = platformCatalog(), installedToolchains = []) {
|
|
182
|
+
const inst = new Set(installedToolchains);
|
|
183
|
+
const lines = [
|
|
184
|
+
'',
|
|
185
|
+
'Select platform toolchains to install:',
|
|
186
|
+
'',
|
|
187
|
+
" [a] All platforms — RECOMMENDED (~1.5 GB download / ~11 GB extracted).",
|
|
188
|
+
' Press Enter to accept. Installs every toolchain so any Zephyr board',
|
|
189
|
+
" in the data pack builds as-is. Pick individual groups only if you're",
|
|
190
|
+
' consciously trading convenience for disk space.',
|
|
191
|
+
'',
|
|
192
|
+
];
|
|
193
|
+
catalog.forEach((g, i) => {
|
|
194
|
+
const allIn = g.toolchains.length > 0 && g.toolchains.every((t) => inst.has(t));
|
|
195
|
+
const someIn = g.toolchains.some((t) => inst.has(t));
|
|
196
|
+
const mark = allIn ? '[x]' : someIn ? '[~]' : '[ ]';
|
|
197
|
+
const instTag = allIn ? ' installed' : someIn ? ' partial' : '';
|
|
198
|
+
const size = g.size ? ` ${g.size}` : '';
|
|
199
|
+
lines.push(` [${i + 1}] ${g.label.padEnd(48)}${size}${instTag}`);
|
|
200
|
+
});
|
|
201
|
+
lines.push('');
|
|
202
|
+
lines.push("Enter selection (Enter = All; or e.g. '1 2', 'arm,esp32' for a minimal install): ");
|
|
203
|
+
return lines.join('\n');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Interactive checklist: print, read one line, parse. Returns the normalized
|
|
207
|
+
// selection string ('all' or comma-separated group ids).
|
|
208
|
+
export async function platformChecklist(installedToolchains = []) {
|
|
209
|
+
const catalog = platformCatalog();
|
|
210
|
+
output.write(buildChecklist(catalog, installedToolchains));
|
|
211
|
+
const rl = readline.createInterface({ input, output });
|
|
212
|
+
try {
|
|
213
|
+
const answer = await rl.question('');
|
|
214
|
+
return parsePlatformSelection(answer, catalog);
|
|
215
|
+
} finally {
|
|
216
|
+
rl.close();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Try each candidate executable in order; advance on ENOENT so the caller can
|
|
221
|
+
// express "prefer pwsh, fall back to powershell" without probing PATH itself.
|
|
222
|
+
function runWithFallback(exes, args) {
|
|
223
|
+
return new Promise((resolve, reject) => {
|
|
224
|
+
const tryOne = (idx) => {
|
|
225
|
+
if (idx >= exes.length) {
|
|
226
|
+
reject(new Error(`none found on PATH: ${exes.join(', ')}`));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const child = spawn(exes[idx], args, { stdio: 'inherit' });
|
|
230
|
+
child.on('error', (err) => {
|
|
231
|
+
if (err && err.code === 'ENOENT') tryOne(idx + 1);
|
|
232
|
+
else reject(err);
|
|
233
|
+
});
|
|
234
|
+
child.on('exit', (code, signal) => resolve({ code, signal }));
|
|
235
|
+
};
|
|
236
|
+
tryOne(0);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Render the --help text. Shows flags, platform groups, and examples.
|
|
241
|
+
// Exported so tests can assert on the content.
|
|
242
|
+
export function buildHelp(v = loadVersionsEnv()) {
|
|
243
|
+
const catalog = platformCatalog(v);
|
|
244
|
+
const groups = catalog
|
|
245
|
+
.map((g, i) => ` ${String(i + 1).padStart(2)}. ${g.label.padEnd(48)} ${g.size}`)
|
|
246
|
+
.join('\n');
|
|
247
|
+
return `
|
|
248
|
+
typeCAD Zephyr installer — cross-platform Zephyr toolchain setup via micromamba.
|
|
249
|
+
|
|
250
|
+
Usage:
|
|
251
|
+
npx --package @typecad/framework-zephyr zephyr-installer [flags]
|
|
252
|
+
|
|
253
|
+
Installs: micromamba + conda env (west, cmake, ninja, dtc, gperf) + the Zephyr
|
|
254
|
+
SDK (selective toolchains or full bundle) + a vanilla west workspace
|
|
255
|
+
(${v.ZEPHYR_MANIFEST_URL || 'zephyr.git'} @ ${v.ZEPHYR_MANIFEST_REV || 'pinned'}).
|
|
256
|
+
|
|
257
|
+
Platform groups (interactive checklist, or --platforms <ids>):
|
|
258
|
+
${groups}
|
|
259
|
+
all. Full bundle (~1.5 GB download, ~11 GB extracted)
|
|
260
|
+
|
|
261
|
+
Flags:
|
|
262
|
+
(none) Interactive: platform checklist → summary → Enter → install.
|
|
263
|
+
--platforms IDS Space-saving subset (comma-separated group ids). The
|
|
264
|
+
default is 'all' — every toolchain, so any board in the
|
|
265
|
+
data pack builds without a follow-up install.
|
|
266
|
+
--modify Re-run the SDK platform step on an existing install:
|
|
267
|
+
ADDS the selected groups' missing toolchains. Nothing is
|
|
268
|
+
deleted unless --prune is also given. SDK-only (skips
|
|
269
|
+
env/workspace).
|
|
270
|
+
--prune With --modify: also DELETE toolchains of platforms not in
|
|
271
|
+
the selection (reclaim disk space). Without it, --modify
|
|
272
|
+
is purely additive.
|
|
273
|
+
--delete UNINSTALL everything: conda env, Zephyr SDK, west
|
|
274
|
+
workspace (+ micromamba itself if no other envs exist).
|
|
275
|
+
Requires typing 'yes' to confirm (or --yes).
|
|
276
|
+
--yes, -y Skip the confirmation prompt (CI / scripting).
|
|
277
|
+
--dry-run Print the resolved plan (URLs, paths, versions) and exit.
|
|
278
|
+
--no-sdk Skip the Zephyr SDK download (env + workspace only).
|
|
279
|
+
--no-workspace Skip west init/update (env + SDK only).
|
|
280
|
+
--env-name NAME Override the conda env name (default: ${v.ENV_NAME || 'zephyr'}).
|
|
281
|
+
--sdk-version V Override the Zephyr SDK version (default: ${v.ZEPHYR_SDK_VERSION || 'pinned'}).
|
|
282
|
+
-h, --help Show this help.
|
|
283
|
+
|
|
284
|
+
Environment overrides:
|
|
285
|
+
MAMBA_ROOT_PREFIX micromamba root (default: ~/micromamba)
|
|
286
|
+
WORKSPACE_DIR west workspace (default: ~/zephyrproject)
|
|
287
|
+
SDK_INSTALL_PARENT where the SDK extracts (default: $MAMBA_ROOT_PREFIX/zephyr-sdk)
|
|
288
|
+
|
|
289
|
+
Examples:
|
|
290
|
+
npx --package @typecad/framework-zephyr zephyr-installer # RECOMMENDED: everything (~1.5 GB)
|
|
291
|
+
npx --package @typecad/framework-zephyr zephyr-installer --platforms arm # space-saver: ARM Cortex-M only (~150 MB)
|
|
292
|
+
npx --package @typecad/framework-zephyr zephyr-installer --platforms arm,esp32 # space-saver: two groups (~450 MB)
|
|
293
|
+
npx --package @typecad/framework-zephyr zephyr-installer --modify --yes # later: add any missing toolchains
|
|
294
|
+
npx --package @typecad/framework-zephyr zephyr-installer --modify --prune --yes # ...and also remove unselected ones
|
|
295
|
+
npx --package @typecad/framework-zephyr zephyr-installer --delete # uninstall everything (confirms)
|
|
296
|
+
npx --package @typecad/framework-zephyr zephyr-installer --dry-run # preview the plan
|
|
297
|
+
node install.mjs --help # same, from a repo checkout
|
|
298
|
+
`.trimStart();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// --- --delete: uninstall everything the installer created -------------------
|
|
302
|
+
|
|
303
|
+
// Recursively sum bytes on disk for a path (best-effort; 0 on any error).
|
|
304
|
+
function dirSize(p) {
|
|
305
|
+
let total = 0;
|
|
306
|
+
const walk = (d) => {
|
|
307
|
+
let entries;
|
|
308
|
+
try { entries = readdirSync(d, { withFileTypes: true }); } catch { return; }
|
|
309
|
+
for (const e of entries) {
|
|
310
|
+
const full = join(d, e.name);
|
|
311
|
+
try {
|
|
312
|
+
if (e.isDirectory()) walk(full);
|
|
313
|
+
else total += statSync(full).size;
|
|
314
|
+
} catch { /* unreadable entry — skip */ }
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
try { walk(p); } catch { /* unreadable root */ }
|
|
318
|
+
return total;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function humanSize(bytes) {
|
|
322
|
+
if (bytes === 0) return '0 B';
|
|
323
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
324
|
+
const i = Math.min(units.length - 1, Math.floor(Math.log(bytes) / Math.log(1024)));
|
|
325
|
+
return `${(bytes / 1024 ** i).toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Resolve the installation layout the same way install.sh/install.ps1 do.
|
|
329
|
+
export function resolveInstallPaths(envName = 'zephyr') {
|
|
330
|
+
const mambaRoot = process.env.MAMBA_ROOT_PREFIX || join(homedir(), 'micromamba');
|
|
331
|
+
return {
|
|
332
|
+
mambaRoot,
|
|
333
|
+
env: join(mambaRoot, 'envs', envName),
|
|
334
|
+
sdkParent: process.env.SDK_INSTALL_PARENT || join(mambaRoot, 'zephyr-sdk'),
|
|
335
|
+
workspace: process.env.WORKSPACE_DIR || join(homedir(), 'zephyrproject'),
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Build the --delete confirmation summary: every path that WILL be removed,
|
|
340
|
+
// with its on-disk size. Pure — exported for unit tests.
|
|
341
|
+
export function buildDeleteSummary(paths = resolveInstallPaths()) {
|
|
342
|
+
const items = [
|
|
343
|
+
['conda env', paths.env],
|
|
344
|
+
['Zephyr SDK', paths.sdkParent],
|
|
345
|
+
['west workspace', paths.workspace],
|
|
346
|
+
];
|
|
347
|
+
const lines = [
|
|
348
|
+
'',
|
|
349
|
+
'typeCAD Zephyr installer — DELETE installation',
|
|
350
|
+
'==============================================',
|
|
351
|
+
'',
|
|
352
|
+
'This permanently removes everything the installer created:',
|
|
353
|
+
'',
|
|
354
|
+
];
|
|
355
|
+
for (const [label, p] of items) {
|
|
356
|
+
if (existsSync(p)) {
|
|
357
|
+
lines.push(` ${label.padEnd(16)} ${p} (${humanSize(dirSize(p))})`);
|
|
358
|
+
} else {
|
|
359
|
+
lines.push(` ${label.padEnd(16)} ${p} (not present — skipped)`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
// micromamba itself: only removable when no other envs exist.
|
|
363
|
+
const otherEnvs = existsSync(paths.env)
|
|
364
|
+
? readdirSync(join(paths.mambaRoot, 'envs'), { withFileTypes: true })
|
|
365
|
+
.filter((e) => e.isDirectory() && e.name !== basename(paths.env))
|
|
366
|
+
.map((e) => e.name)
|
|
367
|
+
: [];
|
|
368
|
+
lines.push('');
|
|
369
|
+
if (otherEnvs.length === 0 && existsSync(paths.mambaRoot)) {
|
|
370
|
+
lines.push(` micromamba root ${paths.mambaRoot} (${humanSize(dirSize(paths.mambaRoot))})`);
|
|
371
|
+
lines.push(' (micromamba itself — removed because it has no other envs)');
|
|
372
|
+
} else if (otherEnvs.length > 0) {
|
|
373
|
+
lines.push(` micromamba root ${paths.mambaRoot} — KEPT (other envs present: ${otherEnvs.join(', ')})`);
|
|
374
|
+
}
|
|
375
|
+
lines.push('');
|
|
376
|
+
lines.push('The shell-profile hook (micromamba shell init) is NOT edited automatically;');
|
|
377
|
+
lines.push('remove its block from ~/.bashrc / your PowerShell $PROFILE if desired.');
|
|
378
|
+
lines.push('');
|
|
379
|
+
lines.push("Type 'yes' to DELETE these directories, anything else to cancel: ");
|
|
380
|
+
return lines.join('\n');
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Perform the deletion. Returns per-item results for reporting.
|
|
384
|
+
export function performDelete(paths = resolveInstallPaths()) {
|
|
385
|
+
const envName = basename(paths.env);
|
|
386
|
+
const targets = [
|
|
387
|
+
['conda env', paths.env],
|
|
388
|
+
['Zephyr SDK', paths.sdkParent],
|
|
389
|
+
['west workspace', paths.workspace],
|
|
390
|
+
];
|
|
391
|
+
// micromamba root only when no other envs remain after removing ours.
|
|
392
|
+
const otherEnvs = existsSync(join(paths.mambaRoot, 'envs'))
|
|
393
|
+
? readdirSync(join(paths.mambaRoot, 'envs'), { withFileTypes: true })
|
|
394
|
+
.filter((e) => e.isDirectory() && e.name !== envName)
|
|
395
|
+
: [];
|
|
396
|
+
if (otherEnvs.length === 0 && existsSync(paths.mambaRoot)) {
|
|
397
|
+
targets.push(['micromamba root', paths.mambaRoot]);
|
|
398
|
+
}
|
|
399
|
+
const results = [];
|
|
400
|
+
for (const [label, p] of targets) {
|
|
401
|
+
if (!existsSync(p)) {
|
|
402
|
+
results.push({ label, path: p, removed: false, note: 'not present' });
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
try {
|
|
406
|
+
rmSync(p, { recursive: true, force: true, maxRetries: 3, retryDelay: 200 });
|
|
407
|
+
results.push({ label, path: p, removed: !existsSync(p) });
|
|
408
|
+
} catch (err) {
|
|
409
|
+
results.push({ label, path: p, removed: false, note: err.message });
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return results;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Only dispatch when invoked directly as `node install.mjs` / via the bin, not
|
|
416
|
+
// when imported (the test suite imports translateToPwsh / buildSummary). Resolve
|
|
417
|
+
// symlinks on both sides: npx and global installs run the bin through a symlink,
|
|
418
|
+
// and path.resolve alone doesn't follow it — without realpathSync the guard
|
|
419
|
+
// evaluates false and the script exits without dispatching (npx "does nothing").
|
|
420
|
+
const realPath = (p) => { try { return realpathSync(p); } catch { return ''; } };
|
|
421
|
+
const invokedDirectly =
|
|
422
|
+
!!process.argv[1] && realPath(fileURLToPath(import.meta.url)) === realPath(process.argv[1]);
|
|
423
|
+
|
|
424
|
+
if (invokedDirectly) {
|
|
425
|
+
const rawArgs = process.argv.slice(2);
|
|
426
|
+
// --help / -h: print + exit before any prompting or dispatch.
|
|
427
|
+
if (rawArgs.includes('--help') || rawArgs.includes('-h')) {
|
|
428
|
+
output.write(buildHelp());
|
|
429
|
+
process.exit(0);
|
|
430
|
+
}
|
|
431
|
+
const dryRun = rawArgs.includes('--dry-run');
|
|
432
|
+
const yes = rawArgs.includes('--yes') || rawArgs.includes('-y');
|
|
433
|
+
const modify = rawArgs.includes('--modify') || rawArgs.includes('-m');
|
|
434
|
+
const prune = rawArgs.includes('--prune');
|
|
435
|
+
const del = rawArgs.includes('--delete') || rawArgs.includes('-d');
|
|
436
|
+
|
|
437
|
+
// --platforms <sel> (non-interactive selection) — read + strip here so the
|
|
438
|
+
// checklist doesn't prompt when it's given.
|
|
439
|
+
let platforms = null;
|
|
440
|
+
{
|
|
441
|
+
const i = rawArgs.indexOf('--platforms');
|
|
442
|
+
if (i >= 0 && i + 1 < rawArgs.length) platforms = rawArgs[i + 1];
|
|
443
|
+
}
|
|
444
|
+
// --yes / -y / --modify / -m / --platforms <v> are consumed here; everything
|
|
445
|
+
// else (--dry-run, --no-sdk, --env-name, ...) is forwarded.
|
|
446
|
+
const forwarded = rawArgs.filter(
|
|
447
|
+
(a, i) =>
|
|
448
|
+
a !== '--yes' && a !== '-y' && a !== '--modify' && a !== '-m' &&
|
|
449
|
+
a !== '--prune' &&
|
|
450
|
+
a !== '--platforms' && rawArgs[i - 1] !== '--platforms',
|
|
451
|
+
);
|
|
452
|
+
// Re-add the resolved platforms as a flag the native scripts understand.
|
|
453
|
+
if (platforms) forwarded.push('--platforms', platforms);
|
|
454
|
+
if (prune) forwarded.push('--prune');
|
|
455
|
+
// --modify skips the env + workspace steps; only the SDK platform step runs.
|
|
456
|
+
if (modify) forwarded.push('--no-workspace');
|
|
457
|
+
|
|
458
|
+
(async () => {
|
|
459
|
+
// --delete: confirm + remove everything, entirely in Node (no dispatch to
|
|
460
|
+
// the native installers — nothing to install). Requires an explicit typed
|
|
461
|
+
// 'yes' (or --yes for scripting); non-TTY without --yes aborts (destructive
|
|
462
|
+
// default-deny, unlike install's proceed-on-non-TTY).
|
|
463
|
+
if (del) {
|
|
464
|
+
const summary = buildDeleteSummary();
|
|
465
|
+
output.write(summary);
|
|
466
|
+
let confirmed = yes;
|
|
467
|
+
if (!yes) {
|
|
468
|
+
if (input.isTTY) {
|
|
469
|
+
const rl = readline.createInterface({ input, output });
|
|
470
|
+
try { confirmed = (await rl.question('')).trim().toLowerCase() === 'yes'; }
|
|
471
|
+
finally { rl.close(); }
|
|
472
|
+
} else {
|
|
473
|
+
output.write('--delete is destructive and stdin is not interactive. Re-run with --yes to proceed.\n');
|
|
474
|
+
process.exit(1);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (!confirmed) {
|
|
478
|
+
output.write('Cancelled — nothing was deleted.\n');
|
|
479
|
+
process.exit(0);
|
|
480
|
+
}
|
|
481
|
+
const results = performDelete();
|
|
482
|
+
for (const r of results) {
|
|
483
|
+
if (r.removed) output.write(` deleted ${r.label.padEnd(16)} ${r.path}\n`);
|
|
484
|
+
else output.write(` skipped ${r.label.padEnd(16)} ${r.path}${r.note ? ` (${r.note})` : ' (not present)'}\n`);
|
|
485
|
+
}
|
|
486
|
+
output.write('\nUninstall complete. (Shell-profile hook left in place — see note above.)\n');
|
|
487
|
+
process.exit(0);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// --modify: warn before any destructive change (deselected toolchains are
|
|
491
|
+
// deleted from disk). The user must acknowledge before the checklist runs.
|
|
492
|
+
if (modify && !dryRun && !yes) {
|
|
493
|
+
output.write(
|
|
494
|
+
[
|
|
495
|
+
'',
|
|
496
|
+
"--modify ADDS the missing toolchains of the selected platforms. Toolchains",
|
|
497
|
+
'of unselected platforms are only removed when --prune is also given;',
|
|
498
|
+
're-downloading a removed platform costs ~100-300 MB per group.',
|
|
499
|
+
'',
|
|
500
|
+
].join('\n'),
|
|
501
|
+
);
|
|
502
|
+
if (input.isTTY) {
|
|
503
|
+
const rl = readline.createInterface({ input, output });
|
|
504
|
+
try {
|
|
505
|
+
await rl.question('Press Enter to continue (Ctrl+C to cancel): ');
|
|
506
|
+
} finally {
|
|
507
|
+
rl.close();
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Resolve the platform selection (interactive checklist unless given).
|
|
513
|
+
if (platforms === null && !dryRun) {
|
|
514
|
+
if (input.isTTY) {
|
|
515
|
+
try {
|
|
516
|
+
platforms = await platformChecklist([]);
|
|
517
|
+
} catch (err) {
|
|
518
|
+
console.error(`typecad-zephyr-install: ${err.message}`);
|
|
519
|
+
process.exit(1);
|
|
520
|
+
}
|
|
521
|
+
// Replace any previously forwarded --platforms with the resolved value.
|
|
522
|
+
const pi = forwarded.lastIndexOf('--platforms');
|
|
523
|
+
if (pi >= 0) forwarded.splice(pi, 2);
|
|
524
|
+
forwarded.push('--platforms', platforms);
|
|
525
|
+
} else {
|
|
526
|
+
// Non-interactive with no --platforms: default to 'all' (full bundle,
|
|
527
|
+
// current behavior) so npx/CI invocations don't hang.
|
|
528
|
+
platforms = 'all';
|
|
529
|
+
forwarded.push('--platforms', 'all');
|
|
530
|
+
}
|
|
531
|
+
} else if (platforms === null) {
|
|
532
|
+
// --dry-run without --platforms: forward without a selection; the native
|
|
533
|
+
// dry-run prints both modes.
|
|
534
|
+
platforms = 'all';
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (!dryRun) {
|
|
538
|
+
output.write(buildSummary(loadVersionsEnv(), detectPlatform(), envNameFromArgs(rawArgs), platforms));
|
|
539
|
+
if (!yes) {
|
|
540
|
+
if (input.isTTY) {
|
|
541
|
+
const rl = readline.createInterface({ input, output });
|
|
542
|
+
try {
|
|
543
|
+
await rl.question('Press Enter to begin (Ctrl+C to cancel): ');
|
|
544
|
+
} finally {
|
|
545
|
+
rl.close();
|
|
546
|
+
}
|
|
547
|
+
} else {
|
|
548
|
+
// Non-interactive stdin (some npx invocations, pipes, CI): can't
|
|
549
|
+
// prompt, so proceed — the user invoked us explicitly. (--yes is the
|
|
550
|
+
// explicit no-prompt flag.) Avoids `npx @typecad/...` aborting when
|
|
551
|
+
// npx doesn't forward a TTY.
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const promise = isWin
|
|
557
|
+
? runWithFallback(
|
|
558
|
+
['pwsh', 'powershell'],
|
|
559
|
+
['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', join(here, 'install.ps1'), ...translateToPwsh(forwarded)],
|
|
560
|
+
)
|
|
561
|
+
: runWithFallback(['bash'], [join(here, 'install.sh'), ...forwarded]);
|
|
562
|
+
|
|
563
|
+
return promise
|
|
564
|
+
.then(({ code, signal }) => {
|
|
565
|
+
if (signal) process.exit(128);
|
|
566
|
+
process.exit(code ?? 1);
|
|
567
|
+
})
|
|
568
|
+
.catch((err) => {
|
|
569
|
+
console.error(`typecad-zephyr-install: ${err.message}`);
|
|
570
|
+
if (isWin) {
|
|
571
|
+
console.error(' Windows needs PowerShell, which ships with Windows 10+.');
|
|
572
|
+
console.error(' If you have Git Bash, you can instead run: bash install.sh');
|
|
573
|
+
} else {
|
|
574
|
+
console.error(' POSIX hosts need bash. Install it, or on Windows use:');
|
|
575
|
+
console.error(' pwsh -File install.ps1 (or powershell -File install.ps1)');
|
|
576
|
+
}
|
|
577
|
+
process.exit(1);
|
|
578
|
+
});
|
|
579
|
+
})();
|
|
580
|
+
}
|