@kvell007/embed-labs-cli 0.1.0-alpha.55 → 0.1.0-alpha.56
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 +128 -3
- package/dist/index.js.map +1 -1
- package/dist/local-toolchain.d.ts +46 -0
- package/dist/local-toolchain.js +239 -5
- package/dist/local-toolchain.js.map +1 -1
- package/package.json +3 -3
|
@@ -116,6 +116,50 @@ export interface LocalToolchainLatestResult {
|
|
|
116
116
|
download?: LocalToolchainDownloadPlan;
|
|
117
117
|
download_error?: string;
|
|
118
118
|
}
|
|
119
|
+
export interface WindowsWslStatusResult {
|
|
120
|
+
host: string;
|
|
121
|
+
platform: string;
|
|
122
|
+
arch: string;
|
|
123
|
+
checked_at: string;
|
|
124
|
+
applicable: boolean;
|
|
125
|
+
wsl_available: boolean;
|
|
126
|
+
usable: boolean;
|
|
127
|
+
distributions: Array<{
|
|
128
|
+
name: string;
|
|
129
|
+
state?: string;
|
|
130
|
+
version?: string;
|
|
131
|
+
default?: boolean;
|
|
132
|
+
}>;
|
|
133
|
+
online_distributions: Array<{
|
|
134
|
+
name: string;
|
|
135
|
+
friendly_name?: string;
|
|
136
|
+
default?: boolean;
|
|
137
|
+
}>;
|
|
138
|
+
commands: {
|
|
139
|
+
status: string;
|
|
140
|
+
list: string;
|
|
141
|
+
list_online: string;
|
|
142
|
+
install_ubuntu: string;
|
|
143
|
+
};
|
|
144
|
+
notes: string[];
|
|
145
|
+
}
|
|
146
|
+
export interface WindowsWslInstallOptions {
|
|
147
|
+
distribution?: string;
|
|
148
|
+
noLaunch?: boolean;
|
|
149
|
+
webDownload?: boolean;
|
|
150
|
+
timeoutMs?: number;
|
|
151
|
+
}
|
|
152
|
+
export interface WindowsWslInstallResult {
|
|
153
|
+
host: string;
|
|
154
|
+
platform: string;
|
|
155
|
+
arch: string;
|
|
156
|
+
command: string[];
|
|
157
|
+
exit_code: number;
|
|
158
|
+
stdout_tail: string[];
|
|
159
|
+
stderr_tail: string[];
|
|
160
|
+
status_after: WindowsWslStatusResult;
|
|
161
|
+
notes: string[];
|
|
162
|
+
}
|
|
119
163
|
export interface LocalToolchainInstallOptions extends LocalToolchainLatestOptions {
|
|
120
164
|
sourceReleaseRoot?: string;
|
|
121
165
|
sourceUrl?: string;
|
|
@@ -281,6 +325,8 @@ export declare function latestLocalToolchain(options?: LocalToolchainLatestOptio
|
|
|
281
325
|
export declare function currentLocalToolchain(installRoot?: string, boardId?: string): Promise<LocalToolchainCurrentResult>;
|
|
282
326
|
export declare function listLocalToolchainEnvironments(options?: LocalToolchainListOptions): Promise<LocalToolchainListResult>;
|
|
283
327
|
export declare function installLocalToolchain(options?: LocalToolchainInstallOptions): Promise<LocalToolchainInstallResult>;
|
|
328
|
+
export declare function windowsWslStatus(): Promise<WindowsWslStatusResult>;
|
|
329
|
+
export declare function windowsWslInstall(options?: WindowsWslInstallOptions): Promise<WindowsWslInstallResult>;
|
|
284
330
|
export declare function uninstallLocalToolchain(options?: LocalToolchainUninstallOptions): Promise<LocalToolchainUninstallResult>;
|
|
285
331
|
export declare function validateLocalToolchain(input?: string | {
|
|
286
332
|
releaseRoot?: string;
|
package/dist/local-toolchain.js
CHANGED
|
@@ -205,6 +205,9 @@ export async function latestLocalToolchain(options = {}) {
|
|
|
205
205
|
catch (error) {
|
|
206
206
|
downloadError = error instanceof Error ? error.message : String(error);
|
|
207
207
|
}
|
|
208
|
+
if (!download && isNativeWindowsTaishanPiHost(canonicalBoardId, hostId())) {
|
|
209
|
+
downloadError = taishanPiWindowsWslRequirementMessage();
|
|
210
|
+
}
|
|
208
211
|
return {
|
|
209
212
|
board_id: canonicalBoardId,
|
|
210
213
|
channel: channel.channel,
|
|
@@ -357,6 +360,9 @@ export async function listLocalToolchainEnvironments(options = {}) {
|
|
|
357
360
|
catch (error) {
|
|
358
361
|
downloadError = error instanceof Error ? error.message : String(error);
|
|
359
362
|
}
|
|
363
|
+
if (!download && isNativeWindowsTaishanPiHost(boardId, host)) {
|
|
364
|
+
downloadError = taishanPiWindowsWslRequirementMessage();
|
|
365
|
+
}
|
|
360
366
|
const latestVersion = download?.version ?? board.version;
|
|
361
367
|
const currentForBoard = await currentLocalToolchain(installRoot, boardId);
|
|
362
368
|
const installedCandidate = currentForBoard.installed
|
|
@@ -371,9 +377,12 @@ export async function listLocalToolchainEnvironments(options = {}) {
|
|
|
371
377
|
}
|
|
372
378
|
: undefined;
|
|
373
379
|
const updateAvailable = !!installed?.version && installed.version !== latestVersion;
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
:
|
|
380
|
+
const nativeWindowsTaishanPi = isNativeWindowsTaishanPiHost(boardId, host);
|
|
381
|
+
const effectiveHostSupport = nativeWindowsTaishanPi
|
|
382
|
+
? { supported: false, unsupportedPackages: ["taishanpi-1m-rk3566 native Windows toolchain"] }
|
|
383
|
+
: download || installed
|
|
384
|
+
? { supported: true, unsupportedPackages: [] }
|
|
385
|
+
: hostSupport;
|
|
377
386
|
const status = !effectiveHostSupport.supported
|
|
378
387
|
? "unsupported_host"
|
|
379
388
|
: updateAvailable
|
|
@@ -410,8 +419,8 @@ export async function listLocalToolchainEnvironments(options = {}) {
|
|
|
410
419
|
},
|
|
411
420
|
packages,
|
|
412
421
|
components: download?.components?.map(localToolchainEnvironmentComponent),
|
|
413
|
-
install_command: `embedlabs local toolchain install --board ${boardId} --mode ${mode}`,
|
|
414
|
-
update_command: `embedlabs local toolchain install --board ${boardId} --mode ${mode} --force`,
|
|
422
|
+
install_command: nativeWindowsTaishanPi ? "embedlabs local wsl status" : `embedlabs local toolchain install --board ${boardId} --mode ${mode}`,
|
|
423
|
+
update_command: nativeWindowsTaishanPi ? "embedlabs local wsl status" : `embedlabs local toolchain install --board ${boardId} --mode ${mode} --force`,
|
|
415
424
|
notes: environmentNotes({ boardId, status, downloadError, unsupportedPackages: effectiveHostSupport.unsupportedPackages })
|
|
416
425
|
});
|
|
417
426
|
}
|
|
@@ -430,6 +439,9 @@ export async function listLocalToolchainEnvironments(options = {}) {
|
|
|
430
439
|
}
|
|
431
440
|
export async function installLocalToolchain(options = {}) {
|
|
432
441
|
const latest = await latestLocalToolchain(options);
|
|
442
|
+
if (isNativeWindowsTaishanPiHost(latest.board_id, latest.host)) {
|
|
443
|
+
throw new Error(taishanPiWindowsWslRequirementMessage());
|
|
444
|
+
}
|
|
433
445
|
const installRoot = resolveInstallRoot(options.installRoot);
|
|
434
446
|
const releaseRoot = resolve(installRoot, "toolchains", latest.board_id, latest.version);
|
|
435
447
|
const installMode = normalizeLocalToolchainInstallMode(options.mode ?? latest.download?.default_mode);
|
|
@@ -511,6 +523,97 @@ export async function installLocalToolchain(options = {}) {
|
|
|
511
523
|
await rm(tempDir, { recursive: true, force: true });
|
|
512
524
|
}
|
|
513
525
|
}
|
|
526
|
+
export async function windowsWslStatus() {
|
|
527
|
+
const result = {
|
|
528
|
+
host: hostId(),
|
|
529
|
+
platform: platform(),
|
|
530
|
+
arch: arch(),
|
|
531
|
+
checked_at: new Date().toISOString(),
|
|
532
|
+
applicable: platform() === "win32",
|
|
533
|
+
wsl_available: false,
|
|
534
|
+
usable: false,
|
|
535
|
+
distributions: [],
|
|
536
|
+
online_distributions: [],
|
|
537
|
+
commands: {
|
|
538
|
+
status: "wsl.exe --status",
|
|
539
|
+
list: "wsl.exe -l -v",
|
|
540
|
+
list_online: "wsl.exe --list --online",
|
|
541
|
+
install_ubuntu: "wsl.exe --install -d Ubuntu --web-download"
|
|
542
|
+
},
|
|
543
|
+
notes: []
|
|
544
|
+
};
|
|
545
|
+
if (!result.applicable) {
|
|
546
|
+
result.notes.push("WSL2 is only required for TaishanPi local development on Windows hosts.");
|
|
547
|
+
return result;
|
|
548
|
+
}
|
|
549
|
+
const whereWsl = await runCommand(["cmd", "/c", "where wsl.exe"], homedir());
|
|
550
|
+
result.wsl_available = whereWsl.exit_code === 0;
|
|
551
|
+
if (!result.wsl_available) {
|
|
552
|
+
result.notes.push("wsl.exe was not found. Enable Windows Subsystem for Linux before installing TaishanPi local tools.");
|
|
553
|
+
return result;
|
|
554
|
+
}
|
|
555
|
+
const list = await runCommand(["wsl.exe", "-l", "-v"], homedir());
|
|
556
|
+
const normalized = normalizeWindowsCommandText([...list.stdout_tail, ...list.stderr_tail].join("\n"));
|
|
557
|
+
result.distributions = list.exit_code === 0 ? parseWslDistributionList(normalized) : [];
|
|
558
|
+
const online = await runCommand(["wsl.exe", "--list", "--online"], homedir());
|
|
559
|
+
const normalizedOnline = normalizeWindowsCommandText([...online.stdout_tail, ...online.stderr_tail].join("\n"));
|
|
560
|
+
result.online_distributions = online.exit_code === 0 ? parseWslOnlineDistributionList(normalizedOnline) : [];
|
|
561
|
+
result.usable = list.exit_code === 0 && result.distributions.length > 0;
|
|
562
|
+
if (!result.usable) {
|
|
563
|
+
result.notes.push("No usable WSL2 distribution is configured yet. Install Ubuntu, restart Windows if requested, then run Embed Labs TaishanPi tools inside WSL2.");
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
result.notes.push("Run TaishanPi local compile, Qt, and image tooling inside the listed WSL2 distribution. Native Windows TaishanPi toolchain packages are intentionally not published.");
|
|
567
|
+
}
|
|
568
|
+
return result;
|
|
569
|
+
}
|
|
570
|
+
export async function windowsWslInstall(options = {}) {
|
|
571
|
+
const distro = options.distribution?.trim() || "Ubuntu";
|
|
572
|
+
const command = ["wsl.exe", "--install", "-d", distro];
|
|
573
|
+
if (options.noLaunch !== false) {
|
|
574
|
+
command.push("--no-launch");
|
|
575
|
+
}
|
|
576
|
+
if (options.webDownload !== false) {
|
|
577
|
+
command.push("--web-download");
|
|
578
|
+
}
|
|
579
|
+
const notes = [];
|
|
580
|
+
if (platform() !== "win32") {
|
|
581
|
+
notes.push("WSL installation can only run on Windows hosts.");
|
|
582
|
+
return {
|
|
583
|
+
host: hostId(),
|
|
584
|
+
platform: platform(),
|
|
585
|
+
arch: arch(),
|
|
586
|
+
command,
|
|
587
|
+
exit_code: 1,
|
|
588
|
+
stdout_tail: [],
|
|
589
|
+
stderr_tail: [],
|
|
590
|
+
status_after: await windowsWslStatus(),
|
|
591
|
+
notes
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
const result = await runCommandWithTimeout(command, homedir(), options.timeoutMs ?? 600_000);
|
|
595
|
+
const statusAfter = await windowsWslStatus();
|
|
596
|
+
if (result.exit_code === 0) {
|
|
597
|
+
notes.push("WSL install command completed. If Windows requests a restart, restart before running TaishanPi local tools.");
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
notes.push("WSL install command did not complete successfully. Run the same command in an elevated Windows terminal if the error indicates permissions or reboot requirements.");
|
|
601
|
+
}
|
|
602
|
+
if (!statusAfter.usable) {
|
|
603
|
+
notes.push("WSL2 is still not usable after this command. Complete first-launch setup for the distribution, then rerun embedlabs local wsl status.");
|
|
604
|
+
}
|
|
605
|
+
return {
|
|
606
|
+
host: hostId(),
|
|
607
|
+
platform: platform(),
|
|
608
|
+
arch: arch(),
|
|
609
|
+
command,
|
|
610
|
+
exit_code: result.exit_code,
|
|
611
|
+
stdout_tail: result.stdout_tail,
|
|
612
|
+
stderr_tail: result.stderr_tail,
|
|
613
|
+
status_after: statusAfter,
|
|
614
|
+
notes
|
|
615
|
+
};
|
|
616
|
+
}
|
|
514
617
|
export async function uninstallLocalToolchain(options = {}) {
|
|
515
618
|
if (!options.boardId?.trim()) {
|
|
516
619
|
throw new Error("board_id is required for local toolchain uninstall.");
|
|
@@ -1311,6 +1414,10 @@ function environmentNotes(input) {
|
|
|
1311
1414
|
if (isRp2350MonitorBoardId(input.boardId)) {
|
|
1312
1415
|
notes.push("RP2350 Monitor is an optional hardware-control firmware image; the board environment itself is the Pico 2 W or ColorEasyPICO2 C/C++ SDK environment.");
|
|
1313
1416
|
}
|
|
1417
|
+
if (isNativeWindowsTaishanPiHost(input.boardId, hostId())) {
|
|
1418
|
+
notes.push(taishanPiWindowsWslRequirementMessage());
|
|
1419
|
+
notes.push("Check this computer with: embedlabs local wsl status");
|
|
1420
|
+
}
|
|
1314
1421
|
if (input.status === "available") {
|
|
1315
1422
|
notes.push("Environment is available but not installed on this computer.");
|
|
1316
1423
|
}
|
|
@@ -1359,6 +1466,71 @@ function hostId() {
|
|
|
1359
1466
|
}
|
|
1360
1467
|
return `${platform()}-${arch()}`;
|
|
1361
1468
|
}
|
|
1469
|
+
function isNativeWindowsTaishanPiHost(boardId, host) {
|
|
1470
|
+
return normalizeBoardId(boardId) === DEFAULT_BOARD_ID && host.startsWith("win32-");
|
|
1471
|
+
}
|
|
1472
|
+
function taishanPiWindowsWslRequirementMessage() {
|
|
1473
|
+
return "TaishanPi local compile, Qt, image, and Rockchip tooling is supported on Windows through WSL2/Linux only; native Windows TaishanPi packages are not published.";
|
|
1474
|
+
}
|
|
1475
|
+
function normalizeWindowsCommandText(value) {
|
|
1476
|
+
return value.replace(/\0/g, "").replace(/\r/g, "\n");
|
|
1477
|
+
}
|
|
1478
|
+
function parseWslDistributionList(value) {
|
|
1479
|
+
const distributions = [];
|
|
1480
|
+
const lines = value.split(/\n+/)
|
|
1481
|
+
.map((line) => line.trim())
|
|
1482
|
+
.filter(Boolean);
|
|
1483
|
+
for (const rawLine of lines) {
|
|
1484
|
+
if (/^(NAME|Windows Subsystem|Usage|Copyright|\-|用法|选项|命令)/i.test(rawLine)) {
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1487
|
+
const isDefault = rawLine.startsWith("*");
|
|
1488
|
+
const line = rawLine.replace(/^\*\s*/, "").trim();
|
|
1489
|
+
const parts = line.split(/\s+/).filter(Boolean);
|
|
1490
|
+
if (parts.length === 0 || parts[0].includes(":")) {
|
|
1491
|
+
continue;
|
|
1492
|
+
}
|
|
1493
|
+
const maybeVersion = parts.at(-1);
|
|
1494
|
+
const version = maybeVersion && /^[12]$/.test(maybeVersion) ? maybeVersion : undefined;
|
|
1495
|
+
const state = version && parts.length >= 3 ? parts.at(-2) : parts.length >= 2 ? parts.at(-1) : undefined;
|
|
1496
|
+
distributions.push({
|
|
1497
|
+
name: parts[0],
|
|
1498
|
+
state,
|
|
1499
|
+
version,
|
|
1500
|
+
default: isDefault
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
return distributions;
|
|
1504
|
+
}
|
|
1505
|
+
function parseWslOnlineDistributionList(value) {
|
|
1506
|
+
const distributions = [];
|
|
1507
|
+
const lines = value.split(/\n+/)
|
|
1508
|
+
.map((line) => line.trim())
|
|
1509
|
+
.filter(Boolean);
|
|
1510
|
+
let inTable = false;
|
|
1511
|
+
for (const rawLine of lines) {
|
|
1512
|
+
if (/^NAME\s+FRIENDLY NAME/i.test(rawLine)) {
|
|
1513
|
+
inTable = true;
|
|
1514
|
+
continue;
|
|
1515
|
+
}
|
|
1516
|
+
if (!inTable) {
|
|
1517
|
+
continue;
|
|
1518
|
+
}
|
|
1519
|
+
const isDefault = rawLine.startsWith("*");
|
|
1520
|
+
const line = rawLine.replace(/^\*\s*/, "").trim();
|
|
1521
|
+
const parts = line.split(/\s{2,}/).filter(Boolean);
|
|
1522
|
+
const name = parts[0]?.trim();
|
|
1523
|
+
if (!name || name.includes(":")) {
|
|
1524
|
+
continue;
|
|
1525
|
+
}
|
|
1526
|
+
distributions.push({
|
|
1527
|
+
name,
|
|
1528
|
+
friendly_name: parts.slice(1).join(" ").trim() || undefined,
|
|
1529
|
+
default: isDefault
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
return distributions;
|
|
1533
|
+
}
|
|
1362
1534
|
function resolveInstallRoot(installRoot) {
|
|
1363
1535
|
return resolve(installRoot
|
|
1364
1536
|
|| process.env.EMBEDLABS_HOME?.trim()
|
|
@@ -1800,6 +1972,68 @@ async function runCommand(command, cwd) {
|
|
|
1800
1972
|
});
|
|
1801
1973
|
});
|
|
1802
1974
|
}
|
|
1975
|
+
async function runCommandWithTimeout(command, cwd, timeoutMs) {
|
|
1976
|
+
return await new Promise((resolve) => {
|
|
1977
|
+
const child = spawn(command[0], command.slice(1), {
|
|
1978
|
+
cwd,
|
|
1979
|
+
env: process.env,
|
|
1980
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1981
|
+
});
|
|
1982
|
+
let stdout = "";
|
|
1983
|
+
let stderr = "";
|
|
1984
|
+
let settled = false;
|
|
1985
|
+
const timer = setTimeout(() => {
|
|
1986
|
+
if (settled) {
|
|
1987
|
+
return;
|
|
1988
|
+
}
|
|
1989
|
+
stderr += `Command timed out after ${timeoutMs} ms.\n`;
|
|
1990
|
+
child.kill("SIGTERM");
|
|
1991
|
+
setTimeout(() => {
|
|
1992
|
+
if (!settled) {
|
|
1993
|
+
child.kill("SIGKILL");
|
|
1994
|
+
}
|
|
1995
|
+
}, 2_000).unref();
|
|
1996
|
+
}, timeoutMs);
|
|
1997
|
+
child.stdout.setEncoding("utf8");
|
|
1998
|
+
child.stderr.setEncoding("utf8");
|
|
1999
|
+
child.stdout.on("data", (chunk) => {
|
|
2000
|
+
stdout += chunk;
|
|
2001
|
+
});
|
|
2002
|
+
child.stderr.on("data", (chunk) => {
|
|
2003
|
+
stderr += chunk;
|
|
2004
|
+
});
|
|
2005
|
+
child.on("error", (error) => {
|
|
2006
|
+
if (settled) {
|
|
2007
|
+
return;
|
|
2008
|
+
}
|
|
2009
|
+
settled = true;
|
|
2010
|
+
clearTimeout(timer);
|
|
2011
|
+
stderr += `${error.message}\n`;
|
|
2012
|
+
resolve({
|
|
2013
|
+
command,
|
|
2014
|
+
cwd,
|
|
2015
|
+
exit_code: 127,
|
|
2016
|
+
stdout_tail: tailLines(stdout),
|
|
2017
|
+
stderr_tail: tailLines(stderr)
|
|
2018
|
+
});
|
|
2019
|
+
});
|
|
2020
|
+
child.on("close", (code) => {
|
|
2021
|
+
if (settled) {
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
settled = true;
|
|
2025
|
+
clearTimeout(timer);
|
|
2026
|
+
const timedOut = stderr.includes(`Command timed out after ${timeoutMs} ms.`);
|
|
2027
|
+
resolve({
|
|
2028
|
+
command,
|
|
2029
|
+
cwd,
|
|
2030
|
+
exit_code: timedOut ? 124 : code ?? 1,
|
|
2031
|
+
stdout_tail: tailLines(stdout),
|
|
2032
|
+
stderr_tail: tailLines(stderr)
|
|
2033
|
+
});
|
|
2034
|
+
});
|
|
2035
|
+
});
|
|
2036
|
+
}
|
|
1803
2037
|
async function fileInfoFor(filePath) {
|
|
1804
2038
|
const result = await runCommand(["file", filePath], dirname(filePath));
|
|
1805
2039
|
return result.exit_code === 0 ? result.stdout_tail.join("\n") : undefined;
|