@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.
@@ -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 = 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 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(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;