Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
1ff4c98
Refactor board commands.
maejima-fumika Jun 26, 2026
1edb4c2
Update tests.
maejima-fumika Jun 27, 2026
29683e5
Fix bugs.
maejima-fumika Jun 27, 2026
b59ebad
Refactor lang.
maejima-fumika Jun 30, 2026
13421fc
Refactor lang.
maejima-fumika Jun 30, 2026
cd10de6
Add windows to lang.
maejima-fumika Jun 30, 2026
1616c1c
fix on windows.
maejima-fumika Jul 2, 2026
06dacb8
Adding windows to cli.
maejima-fumika Jul 2, 2026
c4ca87a
Merge branch 'feature/add-windows-to-platform-windows' into feature/a…
maejima-fumika Jul 2, 2026
9b302a0
Add windows to lang test.
maejima-fumika Jul 2, 2026
7f31b7b
Add changes to run lang esp32 tests on windows.
maejima-fumika Jul 3, 2026
43b4f13
Merge branch 'feature/add-windows-to-platform-windows' into feature/a…
maejima-fumika Jul 3, 2026
8e4e165
Add windows to cli
maejima-fumika Jul 4, 2026
eeed07d
Fix cli integration tests for windows.
maejima-fumika Jul 4, 2026
b1d6ba4
Debugging
maejima-fumika Jul 4, 2026
9633344
Fix cli integration tests for windows.
maejima-fumika Jul 4, 2026
a1284c4
Fix cli to run run command on windows.
maejima-fumika Jul 4, 2026
e7aeb9d
Merge branch 'feature/add-windows-to-platform-windows' into feature/a…
maejima-fumika Jul 4, 2026
22406c0
Remove shell true
maejima-fumika Jul 4, 2026
4032645
Fix run with repl.
maejima-fumika Jul 4, 2026
48a6e48
Merge
maejima-fumika Jul 4, 2026
88b3a7e
Fix promise bugs on ble.
maejima-fumika Jul 5, 2026
1abc4f3
Update open url operation for notebook.
maejima-fumika Jul 5, 2026
adf98a2
Shorten setup messages.
maejima-fumika Jul 5, 2026
7bfa26d
Update command execution.
maejima-fumika Jul 5, 2026
6779882
Fixing bugs on windows
maejima-fumika Jul 5, 2026
2f8332c
Fix parse bugs.
maejima-fumika Jul 5, 2026
057a8c4
Fixing link bugs for windows.
maejima-fumika Jul 5, 2026
8641a50
Fixing link bugs.
maejima-fumika Jul 5, 2026
56948bf
Fixing link bugs for windows.
maejima-fumika Jul 5, 2026
8511f51
Fixing link bugs.
maejima-fumika Jul 5, 2026
a1b32e5
Fixing link bugs for windows.
maejima-fumika Jul 5, 2026
c94530c
Fixing unit tests.
maejima-fumika Jul 5, 2026
84665c6
Fix python command usage.
maejima-fumika Jul 5, 2026
412aecb
Fix update command.
maejima-fumika Jul 6, 2026
5cc444e
Fix update test.
maejima-fumika Jul 7, 2026
0072757
Update document.
maejima-fumika Jul 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Build and test the CLI package:
cd cli
npm run build
npm test # unit tests only
npm run test:integration # host integration tests (macOS + cc)
npm run test:integration # host integration tests (macOS or Windows)
npm run test:all # unit + integration
```

Expand All @@ -27,10 +27,12 @@ npm run test:all # unit + integration
| Script | Jest project | Location | Notes |
| :--- | :--- | :--- | :--- |
| `npm test` | `unit` | `tests/**/*.test.ts` (excludes `integration/`) | Mocks fs, shell, logger, devices |
| `npm run test:integration` | `integration` | `tests/integration/**/*.test.ts` | Real host `shell` process; macOS only |
| `npm run test:integration` | `integration` | `tests/integration/**/*.test.ts` | Real host `shell` process; macOS or Windows (MinGW-w64) |
| `npm run test:all` | both | — | Run before merging CLI changes |

**Integration test requirements:** macOS, `cc`, and the `microcontroller/` tree at the repository root. On first run, tests build `microcontroller/ports/host/build/shell` and `c-runtime.so` if missing. Tests are skipped automatically on non-macOS platforms.
**Integration test requirements:** macOS (`cc`) or Windows (MinGW-w64: `gcc`, `mingw32-make`), and the `microcontroller/` tree at the repository root. On first run, tests build `microcontroller/ports/host/build/shell` (or `shell.exe`) and `c-runtime.so` (or `c-runtime.dll`) if missing. Tests are skipped automatically on Linux and other unsupported platforms.

**Supported platforms:** macOS and Windows for `host` and `esp32` board setup. Linux is not supported. On Windows, install the Visual C++ Build Environment before `npm install` (node-gyp), and MinGW-w64 for the host runtime. See [Windows prerequisites](https://csg-tokyo.github.io/bluescript/docs/tutorial/get-started/setup-environment-windows) on the website.

**Integration coverage (14 tests):**

Expand Down Expand Up @@ -61,7 +63,7 @@ bscript -v

Before merging CLI changes or cutting a release:

1. Run `npm run test:all` (or at least `npm test`; on macOS also `npm run test:integration`).
1. Run `npm run test:all` (or at least `npm test`; on macOS or Windows also `npm run test:integration`).
2. Follow the manual QA checklist: **[docs/manual-test.md](./docs/manual-test.md)**

- **Daily PRs:** run automated tests plus **Quick smoke (host)** (~15 minutes).
Expand Down
18 changes: 10 additions & 8 deletions cli/docs/manual-test.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,20 @@ Use a clean working directory for project commands (no existing `bsconfig.json`
| Profile | OS | Node.js | Additional requirements |
| :--- | :--- | :--- | :--- |
| **host** | macOS | v18+ (v20+ recommended) | `cc`, `make` |
| **esp32** | macOS | v18+ (v20+ recommended) | ESP32 board, USB cable, Bluetooth enabled |
| **host** | Windows | v18+ (v20+ recommended) | Visual C++ Build Environment (for npm install), MinGW-w64 (`gcc`, `mingw32-make`) |
| **esp32** | macOS | v18+ (v20+ recommended) | Homebrew, Git, Python 3, ESP32 board, USB cable, Bluetooth enabled |
| **esp32** | Windows | v18+ (v20+ recommended) | Visual C++ Build Environment (for npm install), Git, Python 3, `make` or `mingw32-make`, ESP32 board, USB cable, Bluetooth enabled |

> **Note:** The host runtime currently requires **macOS**. ESP32 setup is also macOS-only in the current CLI implementation.
> ESP32 on Windows (setup, flash, BLE `project run`) has been manually verified. Linux is not supported by the CLI.

### Automated integration tests (host)

Host integration tests live in `cli/tests/integration/`. They spawn the real host `shell` binary and exercise `project run` and `repl` end-to-end on disk (no ESP32, no global `bscript` install).

| Requirement | Detail |
| :--- | :--- |
| OS | macOS only (tests are skipped on other platforms) |
| Toolchain | `cc` (builds `microcontroller/ports/host/build/` on first run if missing) |
| OS | macOS or Windows (tests are skipped on Linux and other platforms) |
| Toolchain | macOS: `cc`; Windows: MinGW-w64 (`gcc`, `mingw32-make`). Builds `microcontroller/ports/host/build/` on first run if missing. |
| Repo layout | Run from `cli/` with the `microcontroller/` tree at the repository root |

```bash
Expand All @@ -65,7 +67,7 @@ CLI log output is suppressed during integration runs (`tests/integration-setup.t

## Quick smoke (host only, ~15 min)

Run this before merging most CLI PRs. No hardware required.
Run this before merging most CLI PRs. No hardware required. Run on **macOS** or **Windows** (MinGW-w64 for host). The same steps apply on both platforms.

1. **MT-SMOKE-01** — `bscript -v` prints the expected version
2. **MT-SMOKE-02** — `bscript board list` shows `esp32` and `host`
Expand Down Expand Up @@ -734,11 +736,11 @@ For failures, include the item ID (e.g. `MT-PROJ-RUN-03`) in Notes or link to an

## Coverage map: automated vs manual

Jest **unit** tests in `cli/tests/` mock filesystem, network, and device I/O. **Integration** tests in `cli/tests/integration/` use real host runtime processes on macOS. Use this table to avoid re-testing automated behavior manually while ensuring gaps are covered.
Jest **unit** tests in `cli/tests/` mock filesystem, network, and device I/O. **Integration** tests in `cli/tests/integration/` use real host runtime processes on macOS or Windows. Use this table to avoid re-testing automated behavior manually while ensuring gaps are covered.

| Area | Unit tests | Integration tests (host, macOS) | Manual testing still needed |
| Area | Unit tests | Integration tests (host, macOS/Windows) | Manual testing still needed |
| :--- | :--- | :--- | :--- |
| `board setup` | Handler logic, macOS paths, skip-if-done | — | Real download, ESP-IDF install, host runtime build |
| `board setup` | Handler logic, macOS/Windows paths, skip-if-done | — | Real download, ESP-IDF install, host runtime build |
| `board flash-runtime` | ESP32 handler, host rejection, port prompt mocked, `deviceName` passed to build | — | Actual USB flash on hardware; BLE advertised name after flash |
| `board remove` / `fullclean` | File removal, prompts mocked | — | Confirm disk state after real removal |
| `board update` | Update steps, rollback logic | — | End-to-end after real version bump |
Expand Down
21 changes: 15 additions & 6 deletions cli/src/commands/board/flash-runtime.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { Command } from "commander";
import inquirer from 'inquirer';
import * as path from 'path';
import * as os from 'os';
import { SerialPort } from 'serialport'
import { BoardName } from "../../config/board-utils";
import { logger, runStep } from "../../core/logger";
import { exec } from '../../core/shell';
import { execShell } from '../../core/command-exec';
import chalk from "chalk";
import { CommandHandler } from "../command";
import { CommandHandlerWithUpdateCheck } from "../command";
import { DEFAULT_DEVICE_NAME } from "../../config/project-config";


const RUNTIME_ESP_PORT_DIR = (runtimeDir: string) => path.join(runtimeDir, 'ports/esp32');

abstract class FlashRuntimeHandler extends CommandHandler {
abstract class FlashRuntimeHandler extends CommandHandlerWithUpdateCheck {
abstract isSetup(): boolean;
abstract flashRuntime(port: string, deviceName?: string): Promise<void>;

Expand Down Expand Up @@ -41,11 +42,19 @@ class ESP32FlashRuntimeHandler extends FlashRuntimeHandler {

deviceName = deviceName ?? DEFAULT_DEVICE_NAME;

await exec(
`source ${boardConfig.exportFile} && idf.py -D DEVICE_NAME=${deviceName} build flash -p ${port}`,
{ cwd: RUNTIME_ESP_PORT_DIR(runtimeDir) }
await this.runIdfPy(
boardConfig.exportFile,
['-D', `DEVICE_NAME=${deviceName}`, 'build', 'flash', '-p', port],
RUNTIME_ESP_PORT_DIR(runtimeDir)
)
}

private async runIdfPy(exportFile: string, args: string[], cwd: string) {
const osType = os.platform();
const preCommand = osType === 'win32' ? `call ${exportFile}` : `source ${exportFile}`;

await execShell(`${preCommand} && idf.py ${args.join(' ')}`, { cwd });
}
}

function getFlashRuntimeHandler(board: string) {
Expand Down
10 changes: 4 additions & 6 deletions cli/src/commands/board/full-clean.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import { Command } from "commander";
import inquirer from 'inquirer';
import { logger } from "../../core/logger";
import * as fs from '../../core/fs';
import { CommandHandler } from "../command";
import { GLOBAL_SETTINGS } from "../../config/constants";
import { CommonBoardEnv } from "../../platforms/board-env";


class FullcleanHandler extends CommandHandler {
constructor() {
super(false);
super();
}

fullclean() {
if (fs.exists(GLOBAL_SETTINGS.BLUESCRIPT_DIR)) {
fs.removeDir(GLOBAL_SETTINGS.BLUESCRIPT_DIR);
}
const env = new CommonBoardEnv();
env.removeBlueScriptDir();
}
}

Expand Down
4 changes: 2 additions & 2 deletions cli/src/commands/board/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { Command } from "commander";
import chalk from 'chalk';
import { BOARD_NAMES } from "../../config/board-utils";
import { logger } from "../../core/logger";
import { CommandHandler } from "../command";
import { CommandHandlerWithUpdateCheck } from "../command";


class ListHandler extends CommandHandler {
class ListHandler extends CommandHandlerWithUpdateCheck {
list() {
const supportedBoards = BOARD_NAMES;
logger.log('Available boards:');
Expand Down
71 changes: 18 additions & 53 deletions cli/src/commands/board/remove.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,38 @@
import { Command } from "commander";
import inquirer from 'inquirer';
import { BoardName } from "../../config/board-utils";
import { BoardName, isValidBoard } from "../../config/board-utils";
import { logger, runStep } from "../../core/logger";
import * as fs from '../../core/fs';
import { CommandHandler } from "../command";
import { CommandHandlerWithUpdateCheck } from "../command";
import { BoardEnv, createBoardEnv } from "../../platforms/board-env";


abstract class RemoveHandler extends CommandHandler {
async remove() {
await runStep('Removing...', () => this.removeBoard());
this.globalConfigHandler.save();
}
abstract isSetup(): boolean;
abstract removeBoard(): Promise<void>;
}
class RemoveHandler extends CommandHandlerWithUpdateCheck {
boardName: BoardName;
boardEnv: BoardEnv;

class HostRemoveHandler extends RemoveHandler {
readonly boardName: BoardName = 'host';

isSetup(): boolean {
return this.globalConfigHandler.isBoardSetup(this.boardName);
constructor(boardName: BoardName) {
super();
this.boardName = boardName;
this.boardEnv = createBoardEnv(boardName);
}

async removeBoard() {
const boardConfig = this.globalConfigHandler.getBoardConfig('host');
if (boardConfig === undefined) {
throw new Error(`Cannot find config for ${this.boardName}.`);
}
if (fs.exists(boardConfig.buildDir)) {
fs.removeDir(boardConfig.buildDir);
}

async remove() {
await runStep('Removing...', async () => this.boardEnv.removeBoardRoot());
this.globalConfigHandler.removeBoardConfig(this.boardName);
this.globalConfigHandler.save();
}
}

class ESP32RemoveHandler extends RemoveHandler {
readonly boardName: BoardName = 'esp32';


isSetup(): boolean {
return this.globalConfigHandler.isBoardSetup(this.boardName);
}

async removeBoard() {
const boardConfig = this.globalConfigHandler.getBoardConfig('esp32');
if (boardConfig === undefined) {
throw new Error(`Cannot find config for ${this.boardName}.`);
}
if (fs.exists(boardConfig.rootDir)) {
fs.removeDir(boardConfig.rootDir);
}

this.globalConfigHandler.removeBoardConfig(this.boardName);
}
}

function getRemoveHandler(board: string) {
if (board === 'esp32') {
return new ESP32RemoveHandler();
}
if (board === 'host') {
return new HostRemoveHandler();
}
throw new Error(`Unsupported board name: ${board}`);
}

export async function handleRemoveCommand(board: string, options: { force?: boolean }) {
try {
const removeHandler = getRemoveHandler(board);
if (!isValidBoard(board)) {
throw new Error(`Unsupported board name: ${board}`);
}
const removeHandler = new RemoveHandler(board);

// Check if setup has already been completed.
if (!removeHandler.isSetup()) {
Expand Down
Loading
Loading