Skip to content
Open
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
dcc46a8
add jsbinding support
lei9444 May 18, 2026
be1ca74
fix testing code style
lei9444 May 18, 2026
2102060
fix PR comments
lei9444 May 18, 2026
988746a
fix build error
lei9444 May 18, 2026
581f313
fix tests
lei9444 May 18, 2026
89cc5ee
add electron guide
lei9444 May 18, 2026
a142c35
address all comments
lei9444 May 19, 2026
d3115a8
Merge branch 'main' of https://github.com/microsoft/winappCli into le…
lei9444 May 19, 2026
c3fc409
Merge branch 'main' into leilzh/jsbindings
lei9444 May 20, 2026
6506f98
remove jsbindings options
lei9444 May 20, 2026
a3de9d9
Merge branch 'leilzh/jsbindings' of https://github.com/microsoft/wina…
lei9444 May 20, 2026
5c0ed9c
fix ci
lei9444 May 20, 2026
fad88fb
fix all tests
lei9444 May 20, 2026
ea02855
remove code from native
lei9444 May 21, 2026
9d739d5
update doc
lei9444 May 21, 2026
f1babf8
fix all commtents
lei9444 May 21, 2026
d99aeeb
Merge branch 'main' into leilzh/jsbindings
zateutsch May 21, 2026
7bd4e18
fix bug and clean the code change
lei9444 May 22, 2026
4e341a5
Merge branch 'leilzh/jsbindings' of https://github.com/microsoft/wina…
lei9444 May 22, 2026
f29b277
clean code
lei9444 May 22, 2026
33074e8
remove unrelated code
lei9444 May 22, 2026
d55c2b5
clean the code change
lei9444 May 22, 2026
8d995ba
update doc and code clean
lei9444 May 22, 2026
9f2625f
simplfy the comments
lei9444 May 22, 2026
005a4d3
bug fix and new tests added
lei9444 May 22, 2026
196f3f4
fix parse bug
lei9444 May 22, 2026
6dd34a8
fix config not match issue and runtime version auto update
lei9444 May 22, 2026
c140675
Merge branch 'main' into leilzh/jsbindings
nmetulev May 27, 2026
2328bb2
update doc and command logic
lei9444 Jun 1, 2026
f17e5fd
Merge branch 'leilzh/jsbindings' of https://github.com/microsoft/wina…
lei9444 Jun 1, 2026
d0b0128
Merge branch 'main' of https://github.com/microsoft/winappCli into le…
lei9444 Jun 1, 2026
fb17d12
fix lint and doc
lei9444 Jun 1, 2026
8b9b4d1
fix local comments
lei9444 Jun 3, 2026
f515030
Merge branch 'main' of https://github.com/microsoft/winappCli into le…
lei9444 Jun 3, 2026
0c62298
fix comments and local PR review
lei9444 Jun 4, 2026
2d822b6
Merge branch 'main' of https://github.com/microsoft/winappCli into le…
lei9444 Jun 4, 2026
0098260
update the framework skill description
lei9444 Jun 4, 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
6 changes: 5 additions & 1 deletion .github/plugin/agents/winapp.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,12 +222,16 @@ Want to inspect or interact with a running app's UI?
## Framework-specific guidance

### Electron
- **Setup:** `winapp init --use-defaults` → `winapp node create-addon --template cs` (or `--template cpp`) → `winapp node add-electron-debug-identity`
- **Setup:** `winapp init --use-defaults` → choose your Windows API access path:
- **JS bindings:** typed `.winapp/bindings/*.{js,d.ts}` for the Windows App SDK via the `@microsoft/dynwinrt` runtime (no native build step). Opt in at the `winapp init` prompt above.
- **Native addons:** `winapp node create-addon --template cs` (or `--template cpp`) for C#/C++ addons when you need full WinRT access or stateful native services.
- **Package:** Build with your packager (e.g., Electron Forge), then `winapp package <dist> --cert .\devcert.pfx`
- Use `winapp node create-addon` to create native C#/C++ addons for Windows APIs
- Regenerate bindings after edits: `npx winapp restore` for `winapp.yaml` changes (also refreshes bindings), or the faster `npx winapp node generate-bindings` for `winapp.jsBindings`-only changes.
- Use `winapp node add-electron-debug-identity` / `clear-electron-debug-identity` for identity management
- **⚠️ Always run `npx winapp node add-electron-debug-identity` before testing any Windows API that requires package identity** — without this, APIs will fail at runtime
- Guide: https://github.com/microsoft/WinAppCli/blob/main/docs/guides/electron/setup.md
- JS bindings reference: https://github.com/microsoft/WinAppCli/blob/main/docs/guides/electron/js-file-picker.md

### .NET (WPF, WinForms, Console)
- **Setup:** `winapp init --use-defaults` — but if you already have a `Package.appxmanifest` (e.g., WinUI 3 apps), you likely **don't need `winapp init`**. Just ensure your `.csproj` references the `Microsoft.WindowsAppSDK` NuGet package and has the right properties for packaged builds.
Expand Down
14 changes: 14 additions & 0 deletions .github/plugin/skills/winapp-cli/frameworks/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Use the **npm package** (`@Microsoft/WinAppCli`), **not** the standalone CLI. Th
- The native winapp CLI binary bundled inside `node_modules`
- A Node.js SDK with helpers for creating native C#/C++ addons
- Electron-specific commands under `npx winapp node`
- JS bindings for calling Windows App SDK APIs directly from JavaScript — no native addon required

Quick start:
```powershell
Expand All @@ -39,7 +40,20 @@ npx winapp node create-addon --template cs # create a C# native addon
npx winapp node add-electron-debug-identity # register identity for debugging
```

#### Choosing between JS bindings and a native addon

**Default — Windows App SDK API → JS bindings.** You can call virtually all Windows App SDK APIs (Notifications, FilePickers, AI like `TextRecognizer` / `LanguageModel`, etc.) directly from JavaScript, excluding UI APIs (WinUI / XAML controls). See [`@microsoft/dynwinrt` scope](https://github.com/microsoft/dynwinrt#scope).

**Fall back to `node create-addon` when there's no `.winmd`:**

- **Win32 / pure COM** (P/Invoke, raw `IFileDialog`, registry, custom COM servers) → `--template cpp`
- **C++ library** (headers + static/shared lib only) → `--template cpp`
- **Managed .NET assembly only** (vendor SDK) → `--template cs` ([node-api-dotnet](https://github.com/microsoft/node-api-dotnet))

Mixing both in one app is normal.

Additional Electron guides:
- [JS bindings guide](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/electron/js-file-picker.md) — end-to-end workflow for calling WinRT from JS/TS, including binding scope configuration
- [Packaging guide](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/electron/packaging.md)
- [C++ notification addon guide](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/electron/cpp-notification-addon.md)
- [WinML addon guide](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/electron/winml-addon.md)
Expand Down
2 changes: 2 additions & 0 deletions .github/plugin/skills/winapp-cli/setup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ After `init`, your project will contain:
- `winapp.yaml` — SDK version pinning for `restore`/`update`
- `.winapp/` — downloaded SDK packages and generated projections
- `.gitignore` update — excludes `.winapp/` and `devcert.pfx`
- `.winapp/bindings/` — generated JS bindings for Windows App SDK APIs (npm-only, Node / Electron)
- `package.json` update — adds the `winapp.jsBindings` namespace and `@microsoft/dynwinrt` dependency (npm-only)

### Restore after cloning

Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/test-samples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,11 @@ jobs:
if: steps.check.outputs.skip != 'true'
shell: pwsh
run: |
Install-Module -Name Pester -Force -SkipPublisherCheck -Scope CurrentUser -MinimumVersion 5.0
if (-not (Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue)) {
Register-PSRepository -Default
}
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
Install-Module -Name Pester -Force -SkipPublisherCheck -Scope CurrentUser -MinimumVersion 5.0 -Repository PSGallery

- name: Run ${{ matrix.sample }} test
if: steps.check.outputs.skip != 'true'
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ out
.nuxt
dist

# Compiled TypeScript unit tests (src/winapp-npm)
dist-test

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
Expand Down
14 changes: 14 additions & 0 deletions docs/fragments/skills/winapp-cli/frameworks.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Use the **npm package** (`@Microsoft/WinAppCli`), **not** the standalone CLI. Th
- The native winapp CLI binary bundled inside `node_modules`
- A Node.js SDK with helpers for creating native C#/C++ addons
- Electron-specific commands under `npx winapp node`
- JS bindings for calling Windows App SDK APIs directly from JavaScript — no native addon required

Quick start:
```powershell
Expand All @@ -34,7 +35,20 @@ npx winapp node create-addon --template cs # create a C# native addon
npx winapp node add-electron-debug-identity # register identity for debugging
```

#### Choosing between JS bindings and a native addon

**Default — Windows App SDK API → JS bindings.** You can call virtually all Windows App SDK APIs (Notifications, FilePickers, AI like `TextRecognizer` / `LanguageModel`, etc.) directly from JavaScript, excluding UI APIs (WinUI / XAML controls). See [`@microsoft/dynwinrt` scope](https://github.com/microsoft/dynwinrt#scope).

**Fall back to `node create-addon` when there's no `.winmd`:**

- **Win32 / pure COM** (P/Invoke, raw `IFileDialog`, registry, custom COM servers) → `--template cpp`
- **C++ library** (headers + static/shared lib only) → `--template cpp`
- **Managed .NET assembly only** (vendor SDK) → `--template cs` ([node-api-dotnet](https://github.com/microsoft/node-api-dotnet))

Mixing both in one app is normal.

Additional Electron guides:
- [JS bindings guide](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/electron/js-file-picker.md) — end-to-end workflow for calling WinRT from JS/TS, including binding scope configuration
- [Packaging guide](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/electron/packaging.md)
- [C++ notification addon guide](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/electron/cpp-notification-addon.md)
- [WinML addon guide](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/electron/winml-addon.md)
Expand Down
2 changes: 2 additions & 0 deletions docs/fragments/skills/winapp-cli/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ After `init`, your project will contain:
- `winapp.yaml` — SDK version pinning for `restore`/`update`
- `.winapp/` — downloaded SDK packages and generated projections
- `.gitignore` update — excludes `.winapp/` and `devcert.pfx`
- `.winapp/bindings/` — generated JS bindings for Windows App SDK APIs (npm-only, Node / Electron)
- `package.json` update — adds the `winapp.jsBindings` namespace and `@microsoft/dynwinrt` dependency (npm-only)

### Restore after cloning

Expand Down
19 changes: 14 additions & 5 deletions docs/guides/electron/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,29 @@ First, you'll set up your development environment with the necessary tools and S

[Get Started with Setup →](setup.md)

### 2. Creating a Native Addon
### 2. Calling Windows APIs

Next, you'll create a native addon that calls Windows APIs. Choose one of the following guides:
Next, choose how to call Windows APIs from your Electron app:

#### Option A: [Creating a C++ Notification Addon](cpp-notification-addon.md)
#### Option A: [JS bindings](js-file-picker.md) ✨ *new*

The simplest path — typed JS bindings generated from `.winmd` metadata, no native build step required from your Electron project. Opt in during `npx winapp init` (or pass `--use-defaults` to auto-accept) and a `.winapp/bindings/` directory is added to your project. You can then add `import { ChatClient } from './.winapp/bindings'` and call WinRT directly.

[Add JS bindings →](js-file-picker.md)

> Native addons (Options B–D below) are still the right choice when the API has no WinRT projection — Win32 / pure COM (raw `IFileDialog`, registry, custom COM servers), C++ libraries that ship only headers + a static/shared lib, or vendor SDKs that ship only a managed .NET assembly. For everything that ships in a `.winmd`, JS bindings are the easier option.

#### Option B: [Creating a C++ Notification Addon](cpp-notification-addon.md)
Learn how to create a C++ addon that calls the Windows App SDK notification APIs. This is a great starting point for understanding native addons before diving into more complex scenarios.

[Create a C++ Notification Addon →](cpp-notification-addon.md)

#### Option B: [Creating a Phi Silica Addon](phi-silica-addon.md)
#### Option C: [Creating a Phi Silica Addon](phi-silica-addon.md)
Learn how to create a C# addon that uses the Phi Silica AI model to summarize text on-device. Phi Silica is a small language model that runs locally on Windows 11 devices with NPUs.

[Create a Phi Silica Addon →](phi-silica-addon.md)

#### Option C: [Creating a WinML Addon](winml-addon.md)
#### Option D: [Creating a WinML Addon](winml-addon.md)
Learn how to create a C# addon that uses Windows Machine Learning (WinML) to run custom ONNX models for image classification, object detection, and more.

[Create a WinML Addon →](winml-addon.md)
Expand All @@ -68,6 +76,7 @@ Finally, you'll package your app as an MSIX for distribution. This includes:
| Phase | Guide | What You'll Learn |
|-------|-------|-------------------|
| 1️⃣ | [Setup](setup.md) | Install tools, initialize SDKs, configure build pipeline |
| 2️⃣ | [JS bindings](js-file-picker.md) | Generate typed JS bindings, no native build step |
| 2️⃣ | [C++ Notification Addon](cpp-notification-addon.md) | Create C++ addon, call notification APIs, test with debug identity |
| 2️⃣ | [Phi Silica Addon](phi-silica-addon.md) | Create C# addon, call AI APIs, test with debug identity |
| 2️⃣ | [WinML Addon](winml-addon.md) | Create C# addon, call WinML APIs, run ONNX models, integrate ML |
Expand Down
194 changes: 194 additions & 0 deletions docs/guides/electron/js-file-picker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
<!-- mslearn: true -->
# Call a Windows File Picker from JavaScript (JS bindings)

This guide shows you how to call a Windows Runtime (WinRT) API — the native Windows file picker — directly from your Electron app's JavaScript, with no native addon and no `node-gyp` / MSBuild step. It's a great starting point for calling any `Windows.*` or `Microsoft.WindowsAppSDK.*` API from JS/TS with full IntelliSense.

## Prerequisites

Before starting this guide, make sure you've:
- Completed the [development environment setup](setup.md).

## Step 1: Confirm your bindings

Setup generated a `.winapp/bindings/` directory next to your sources — one `.js` + `.d.ts` pair per WinRT class, plus an `index.js` that re-exports them all:

```
.winapp/bindings/
├── index.js # entry — re-exports every emitted class
├── index.d.ts # TS bundle
├── FileOpenPicker.js # one pair of files per emitted class
├── FileOpenPicker.d.ts
├── PickerLocationId.js
├── PickerLocationId.d.ts
└── …
```

## Step 2: Call a WinRT API from your Electron code

Load classes from the generated `index.js` — you don't need to know which file inside `.winapp/bindings/` a class lives in. Here's a native file picker (`Microsoft.Windows.Storage.Pickers.FileOpenPicker`) opened from your Electron main process. This API works on any Windows 11 machine once you've wired up debug identity in [Step 3](#step-3-run-it):

```js
// src/index.js (Electron main, CommonJS)
const { app, BrowserWindow, ipcMain } = require('electron');
const {
FileOpenPicker,
PickerLocationId,
PickerViewMode,
} = require('../.winapp/bindings/index.js');

async function pickAnImage(mainWindow) {
// FileOpenPicker needs the parent window's HWND wrapped in a WindowId struct.
// Electron's getNativeWindowHandle() returns an 8-byte buffer on 64-bit Windows.
const hwnd = mainWindow.getNativeWindowHandle().readBigUInt64LE(0);

const picker = FileOpenPicker.createInstance({ value: hwnd });
picker.viewMode = PickerViewMode.Thumbnail;
picker.suggestedStartLocation = PickerLocationId.PicturesLibrary;
picker.fileTypeFilter.replaceAll(['.png', '.jpg', '.jpeg', '.gif']);

const result = await picker.pickSingleFileAsync();
return result?.path; // string with the chosen path, or undefined if the user cancelled
}

// Expose it to the renderer via IPC so a button click can trigger the picker.
ipcMain.handle('pick-image', (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
return pickAnImage(win);
});
```

Then bridge it into the renderer through your preload script:

```js
// src/preload.js
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('winapp', {
pickImage: () => ipcRenderer.invoke('pick-image'),
});
```

Finally, add a button to your renderer and call `window.winapp.pickImage()` when it's clicked:

```html
<!-- src/index.html -->
<button id="pick">Pick an image</button>
<p id="result"></p>

<script>
document.getElementById('pick').addEventListener('click', async () => {
const filePath = await window.winapp.pickImage();
document.getElementById('result').textContent = filePath ?? 'Cancelled';
});
</script>
```

> [!NOTE]
> These examples are CommonJS (`require`). In an ESM project (`"type": "module"` or TypeScript), use a top-level `import` instead:
> ```js
> import { FileOpenPicker, PickerLocationId, PickerViewMode } from '../.winapp/bindings/index.js';
> ```

> [!IMPORTANT]
> Using **Vite**? Externalize `@microsoft/dynwinrt` in `vite.main.config.mjs`:
> ```js
> import { defineConfig } from 'vite';
>
> export default defineConfig({
> build: {
> rollupOptions: {
> external: ['@microsoft/dynwinrt'],
> },
> },
> });
> ```

A few conventions the example shows:

- **Members are camelCase** (`ViewMode` → `viewMode`); names colliding with JS keywords get a trailing underscore (`default_`, `delete_`).
- **Construct via static factories, not `new`** — `FileOpenPicker.createInstance(windowId)`.
- **`UInt64` / `Int64` are `bigint`** — use `buffer.readBigUInt64LE(0)` for raw handles, and pass struct wrappers literally (`{ value: hwnd }`).
- **Async methods return a `Promise`**, and collections expose helpers like `replaceAll(...)`, `toArray()`, `for…of`, and `.size`.

## Step 3: Run it

Before the file picker will work, you need to ensure your app runs with identity. Run:

```bash
npx winapp node add-electron-debug-identity
```

> [!NOTE]
> This command is already part of the `postinstall` script we added in the setup guide, so it runs automatically after `npm install`. However, you need to run it manually whenever you modify `Package.appxmanifest`, update app assets, or reinstall dependencies.

Now start the app:

```bash
npm start
```

Click the button and the native Windows file picker appears! 🎉 Importing from `.winapp/bindings/` loads `@microsoft/dynwinrt`, which dispatches each call into the underlying WinRT API — transparent to your code.

## Step 4 (optional): Regenerate after a metadata change

The generated `.winapp/bindings/` files are build artifacts — `.winapp/` is added to `.gitignore` by `init`, so you regenerate them rather than committing. Re-run codegen whenever you change `winapp.yaml` (`packages`, `sdkVersion`, …) or `winapp.jsBindings` in `package.json`:

```bash
# Full restore — refreshes the lockfile (NuGet + cppwinrt headers) and re-runs codegen.
# Use whenever you change `winapp.yaml`.
npx winapp restore

# Fast path — only re-runs dynwinrt-codegen against the cached lockfile.
# Use after editing only `winapp.jsBindings`.
npx winapp node generate-bindings
```

> [!WARNING]
> Treat the output directory (`.winapp/bindings/`) as fully managed by `winapp` — never put hand-written files there. Each regeneration wipes the directory, keeping only the `.dynwinrt-managed` marker `winapp` uses to recognize it as safe to overwrite.

## Customizing the binding scope (optional)

When `jsBindings` is `{}` (the default block `init` adds), bindings are generated for all Windows App SDK APIs in your `winapp.yaml`, minus a few that can't be driven from a headless Node process (XAML/WinUI and WebView2 are excluded by default). To narrow or extend that, configure the `winapp.jsBindings` namespace in `package.json` (the schema lives in `package.json`, not `winapp.yaml`, the same convention used by `eslint`, `jest`, `prettier`, …):

```jsonc
// package.json
{
"winapp": {
"jsBindings": {
// Extra .winmd files to generate bindings from. Each entry is one of:
// { "winmdPath": "..." } emit the whole winmd
// { "winmdPath": "...", "namespace": "...", "classes": [...] } cherry-pick from it
// { "namespace": "Windows.Storage", "classes": [...] } cherry-pick from the Windows SDK
// Paths are relative to the workspace root, or absolute.
"additionalWinmds": [
{ "winmdPath": "vendor/MyCompany.Foo.winmd" },
{ "namespace": "Windows.Storage", "classes": ["StorageFile"] }
],

// Extra .winmd files loaded for type resolution only — never emitted.
"additionalRefs": ["vendor/BigVendor.Common.winmd"]
}
}
}
```

Re-run `npx winapp node generate-bindings` after editing the block. XAML namespaces (`Microsoft.UI.Xaml.*`, `Windows.UI.Xaml.*`) are always out of scope — the codegen can't host them, so no JS is emitted regardless of which packages are installed.

## Next Steps

Congratulations! You're now calling WinRT APIs directly from JavaScript — no native addon, no `node-gyp` build step. 🎉

Now you're ready to:
- **[Package Your App for Distribution](packaging.md)** — produce an MSIX you can ship (the `@microsoft/dynwinrt` runtime is already in your `dependencies`).

Or explore other guides:
- **[Creating a C++ Native Addon](cpp-notification-addon.md)** — for Win32 / pure-COM APIs that have no WinRT projection.
- **[Creating a Phi Silica Addon](phi-silica-addon.md)** — Windows AI APIs from a C# addon.
- **[Getting Started Overview](index.md)** — return to the main guide.

### Additional Resources

- **[winapp CLI Documentation](../../usage.md)** — full CLI reference (`init`, `restore`, `node generate-bindings`).
- **[Sample Electron App](../../../samples/electron/)** — complete working example, including JS bindings.
- **[@microsoft/dynwinrt](https://github.com/microsoft/dynwinrt)** — the runtime that powers the generated bindings.
- **[@microsoft/dynwinrt-codegen](https://www.npmjs.com/package/@microsoft/dynwinrt-codegen)** — the code generator.
Loading
Loading