@kvell007/embed-labs-cli 0.1.0-alpha.55 → 0.1.0-alpha.57

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.
@@ -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;
@@ -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 = taishanPiWindowsRequirementMessage(hostId());
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 = taishanPiWindowsRequirementMessage(host);
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 effectiveHostSupport = download || installed
375
- ? { supported: true, unsupportedPackages: [] }
376
- : hostSupport;
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(taishanPiWindowsRequirementMessage(latest.host));
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,100 @@ 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
+ if (result.host === "win32-arm64") {
569
+ result.notes.push("This Windows ARM64 host cannot run the current TaishanPi linux-x86_64 WSL2 SDK package. Use Windows x64 with WSL2, or publish a linux-arm64/native Windows TaishanPi package before claiming TaishanPi parity on this host.");
570
+ }
571
+ return result;
572
+ }
573
+ export async function windowsWslInstall(options = {}) {
574
+ const distro = options.distribution?.trim() || "Ubuntu";
575
+ const command = ["wsl.exe", "--install", "-d", distro];
576
+ if (options.noLaunch !== false) {
577
+ command.push("--no-launch");
578
+ }
579
+ if (options.webDownload !== false) {
580
+ command.push("--web-download");
581
+ }
582
+ const notes = [];
583
+ if (platform() !== "win32") {
584
+ notes.push("WSL installation can only run on Windows hosts.");
585
+ return {
586
+ host: hostId(),
587
+ platform: platform(),
588
+ arch: arch(),
589
+ command,
590
+ exit_code: 1,
591
+ stdout_tail: [],
592
+ stderr_tail: [],
593
+ status_after: await windowsWslStatus(),
594
+ notes
595
+ };
596
+ }
597
+ const result = await runCommandWithTimeout(command, homedir(), options.timeoutMs ?? 600_000);
598
+ const statusAfter = await windowsWslStatus();
599
+ if (result.exit_code === 0) {
600
+ notes.push("WSL install command completed. If Windows requests a restart, restart before running TaishanPi local tools.");
601
+ }
602
+ else {
603
+ 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.");
604
+ }
605
+ if (!statusAfter.usable) {
606
+ notes.push("WSL2 is still not usable after this command. Complete first-launch setup for the distribution, then rerun embedlabs local wsl status.");
607
+ }
608
+ return {
609
+ host: hostId(),
610
+ platform: platform(),
611
+ arch: arch(),
612
+ command,
613
+ exit_code: result.exit_code,
614
+ stdout_tail: result.stdout_tail,
615
+ stderr_tail: result.stderr_tail,
616
+ status_after: statusAfter,
617
+ notes
618
+ };
619
+ }
514
620
  export async function uninstallLocalToolchain(options = {}) {
515
621
  if (!options.boardId?.trim()) {
516
622
  throw new Error("board_id is required for local toolchain uninstall.");
@@ -1311,6 +1417,10 @@ function environmentNotes(input) {
1311
1417
  if (isRp2350MonitorBoardId(input.boardId)) {
1312
1418
  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
1419
  }
1420
+ if (isNativeWindowsTaishanPiHost(input.boardId, hostId())) {
1421
+ notes.push(taishanPiWindowsRequirementMessage(hostId()));
1422
+ notes.push("Check this computer with: embedlabs local wsl status");
1423
+ }
1314
1424
  if (input.status === "available") {
1315
1425
  notes.push("Environment is available but not installed on this computer.");
1316
1426
  }
@@ -1359,6 +1469,74 @@ function hostId() {
1359
1469
  }
1360
1470
  return `${platform()}-${arch()}`;
1361
1471
  }
1472
+ function isNativeWindowsTaishanPiHost(boardId, host) {
1473
+ return normalizeBoardId(boardId) === DEFAULT_BOARD_ID && host.startsWith("win32-");
1474
+ }
1475
+ function taishanPiWindowsRequirementMessage(host) {
1476
+ if (host === "win32-arm64") {
1477
+ return "TaishanPi local compile, Qt, image, and Rockchip tooling is not available on native Windows ARM64. The current Windows route requires Windows x64 with WSL2/Linux x86_64, or a future linux-arm64/native Windows TaishanPi package.";
1478
+ }
1479
+ return "TaishanPi local compile, Qt, image, and Rockchip tooling is supported on Windows through WSL2/Linux x86_64 only; native Windows TaishanPi packages are not published.";
1480
+ }
1481
+ function normalizeWindowsCommandText(value) {
1482
+ return value.replace(/\0/g, "").replace(/\r/g, "\n");
1483
+ }
1484
+ function parseWslDistributionList(value) {
1485
+ const distributions = [];
1486
+ const lines = value.split(/\n+/)
1487
+ .map((line) => line.trim())
1488
+ .filter(Boolean);
1489
+ for (const rawLine of lines) {
1490
+ if (/^(NAME|Windows Subsystem|Usage|Copyright|\-|用法|选项|命令)/i.test(rawLine)) {
1491
+ continue;
1492
+ }
1493
+ const isDefault = rawLine.startsWith("*");
1494
+ const line = rawLine.replace(/^\*\s*/, "").trim();
1495
+ const parts = line.split(/\s+/).filter(Boolean);
1496
+ if (parts.length === 0 || parts[0].includes(":")) {
1497
+ continue;
1498
+ }
1499
+ const maybeVersion = parts.at(-1);
1500
+ const version = maybeVersion && /^[12]$/.test(maybeVersion) ? maybeVersion : undefined;
1501
+ const state = version && parts.length >= 3 ? parts.at(-2) : parts.length >= 2 ? parts.at(-1) : undefined;
1502
+ distributions.push({
1503
+ name: parts[0],
1504
+ state,
1505
+ version,
1506
+ default: isDefault
1507
+ });
1508
+ }
1509
+ return distributions;
1510
+ }
1511
+ function parseWslOnlineDistributionList(value) {
1512
+ const distributions = [];
1513
+ const lines = value.split(/\n+/)
1514
+ .map((line) => line.trim())
1515
+ .filter(Boolean);
1516
+ let inTable = false;
1517
+ for (const rawLine of lines) {
1518
+ if (/^NAME\s+FRIENDLY NAME/i.test(rawLine)) {
1519
+ inTable = true;
1520
+ continue;
1521
+ }
1522
+ if (!inTable) {
1523
+ continue;
1524
+ }
1525
+ const isDefault = rawLine.startsWith("*");
1526
+ const line = rawLine.replace(/^\*\s*/, "").trim();
1527
+ const parts = line.split(/\s{2,}/).filter(Boolean);
1528
+ const name = parts[0]?.trim();
1529
+ if (!name || name.includes(":")) {
1530
+ continue;
1531
+ }
1532
+ distributions.push({
1533
+ name,
1534
+ friendly_name: parts.slice(1).join(" ").trim() || undefined,
1535
+ default: isDefault
1536
+ });
1537
+ }
1538
+ return distributions;
1539
+ }
1362
1540
  function resolveInstallRoot(installRoot) {
1363
1541
  return resolve(installRoot
1364
1542
  || process.env.EMBEDLABS_HOME?.trim()
@@ -1800,6 +1978,68 @@ async function runCommand(command, cwd) {
1800
1978
  });
1801
1979
  });
1802
1980
  }
1981
+ async function runCommandWithTimeout(command, cwd, timeoutMs) {
1982
+ return await new Promise((resolve) => {
1983
+ const child = spawn(command[0], command.slice(1), {
1984
+ cwd,
1985
+ env: process.env,
1986
+ stdio: ["ignore", "pipe", "pipe"]
1987
+ });
1988
+ let stdout = "";
1989
+ let stderr = "";
1990
+ let settled = false;
1991
+ const timer = setTimeout(() => {
1992
+ if (settled) {
1993
+ return;
1994
+ }
1995
+ stderr += `Command timed out after ${timeoutMs} ms.\n`;
1996
+ child.kill("SIGTERM");
1997
+ setTimeout(() => {
1998
+ if (!settled) {
1999
+ child.kill("SIGKILL");
2000
+ }
2001
+ }, 2_000).unref();
2002
+ }, timeoutMs);
2003
+ child.stdout.setEncoding("utf8");
2004
+ child.stderr.setEncoding("utf8");
2005
+ child.stdout.on("data", (chunk) => {
2006
+ stdout += chunk;
2007
+ });
2008
+ child.stderr.on("data", (chunk) => {
2009
+ stderr += chunk;
2010
+ });
2011
+ child.on("error", (error) => {
2012
+ if (settled) {
2013
+ return;
2014
+ }
2015
+ settled = true;
2016
+ clearTimeout(timer);
2017
+ stderr += `${error.message}\n`;
2018
+ resolve({
2019
+ command,
2020
+ cwd,
2021
+ exit_code: 127,
2022
+ stdout_tail: tailLines(stdout),
2023
+ stderr_tail: tailLines(stderr)
2024
+ });
2025
+ });
2026
+ child.on("close", (code) => {
2027
+ if (settled) {
2028
+ return;
2029
+ }
2030
+ settled = true;
2031
+ clearTimeout(timer);
2032
+ const timedOut = stderr.includes(`Command timed out after ${timeoutMs} ms.`);
2033
+ resolve({
2034
+ command,
2035
+ cwd,
2036
+ exit_code: timedOut ? 124 : code ?? 1,
2037
+ stdout_tail: tailLines(stdout),
2038
+ stderr_tail: tailLines(stderr)
2039
+ });
2040
+ });
2041
+ });
2042
+ }
1803
2043
  async function fileInfoFor(filePath) {
1804
2044
  const result = await runCommand(["file", filePath], dirname(filePath));
1805
2045
  return result.exit_code === 0 ? result.stdout_tail.join("\n") : undefined;