diff --git a/docs/src/components/Mermaid.astro b/docs/src/components/Mermaid.astro
new file mode 100644
index 000000000..65b7e3ca9
--- /dev/null
+++ b/docs/src/components/Mermaid.astro
@@ -0,0 +1,59 @@
+---
+export interface Props {
+ title?: string;
+}
+
+const { title = "" } = Astro.props;
+---
+
+
+
+
+ {title}
+ Loading diagram...
+
+ Source
+
+
+
diff --git a/docs/src/content/docs/contributing/architecture.mdx b/docs/src/content/docs/contributing/architecture.mdx
new file mode 100644
index 000000000..6490a35bc
--- /dev/null
+++ b/docs/src/content/docs/contributing/architecture.mdx
@@ -0,0 +1,165 @@
+---
+title: Wails v3 Architecture
+description: Deep-dive diagrams and explanations of every moving part inside Wails v3
+sidebar:
+ order: 1
+---
+
+import Mermaid from "../../components/Mermaid.astro";
+
+Wails v3 is a **full-stack desktop framework** consisting of a Go runtime,
+a JavaScript bridge, a task-driven tool-chain and a collection of templates that
+let you ship native applications powered by modern web tech.
+
+This page presents the *big picture* in four diagrams:
+
+1. **Overall Architecture** – how every subsystem connects
+2. **Runtime Flow** – what happens when JS calls Go and vice-versa
+3. **Development vs Production** – two modes of the asset server
+4. **Platform Implementations** – where OS-specific code lives
+
+---
+
+## 1 · Overall Architecture
+
+
+flowchart TD
+ subgraph Developer
+ CLI[wails3 CLI]
+ end
+ subgraph Build["Build-time Tool-chain"]
+ GEN["Binding Generator\n(static analysis)"]
+ TMP["Template Engine"]
+ ASSETDEV["Asset Server (dev)"]
+ PKG["Cross-compilation & Packaging"]
+ end
+ subgraph Runtime["Native Runtime"]
+ RT["Desktop Runtime\n(window, dialogs, tray, …)"]
+ BRIDGE["Message Bridge\n(JSON channel)"]
+ end
+ subgraph App["Your Application"]
+ BACKEND["Go Backend"]
+ FRONTEND["Web Frontend\n(React/Vue/…)"]
+ end
+
+ CLI -->|init| TMP
+ CLI -->|generate| GEN
+ CLI -->|dev| ASSETDEV
+ CLI -->|build| PKG
+
+ GEN -->|Go & TS stubs| BACKEND
+ GEN -->|bindings.json| FRONTEND
+
+ ASSETDEV <-->|HTTP| FRONTEND
+
+ BACKEND <--> BRIDGE <--> FRONTEND
+ BRIDGE <--> RT
+ RT <-->|serve assets| ASSETDEV
+
+
+---
+
+## 2 · Runtime Call Flow
+
+
+sequenceDiagram
+ participant JS as JavaScript (frontend)
+ participant Bridge as Bridge (WebView callback)
+ participant MP as Message Processor (Go)
+ participant Go as Bound Go Function
+
+ JS->>Bridge: invoke("Greet","Alice")
+ Bridge->>MP: JSON {t:c,id:42,...}
+ MP->>Go: call Greet("Alice")
+ Go-->>MP: "Hello Alice"
+ MP-->>Bridge: JSON {t:r,id:42,result:"Hello Alice"}
+ Bridge-->>JS: Promise.resolve("Hello Alice")
+
+
+Key points:
+
+* **No HTTP / IPC** – the bridge uses the native WebView’s in-memory channel
+* **Method IDs** – deterministic FNV-hash enables O(1) lookup in Go
+* **Promises** – errors propagate as rejections with stack & code
+
+---
+
+## 3 · Development vs Production Asset Flow
+
+
+flowchart LR
+ subgraph Dev["`wails3 dev`"]
+ VITE["Framework Dev Server\n(port 5173)"]
+ ASDEV["Asset Server (dev)\n(proxy + disk)"]
+ FRONTENDDEV[Browser]
+ end
+ subgraph Prod["`wails3 build`"]
+ EMBED["Embedded FS\n(go:embed)"]
+ ASPROD["Asset Server (prod)\n(read-only)"]
+ FRONTENDPROD[WebView Window]
+ end
+
+ VITE <-->|proxy / HMR| ASDEV
+ ASDEV <-->|http| FRONTENDDEV
+
+ EMBED --> ASPROD
+ ASPROD <-->|in-memory| FRONTENDPROD
+
+
+* In **dev** the server proxies unknown paths to the framework’s live-reload
+ server and serves static assets from disk.
+* In **prod** the same API is backed by `go:embed`, producing a zero-dependency
+ binary.
+
+---
+
+## 4 · Platform-Specific Runtime Split
+
+
+classDiagram
+ class runtime::Window {
+ +Show()
+ +Hide()
+ +Center()
+ }
+
+ runtime::Window <|-- Window_darwin
+ runtime::Window <|-- Window_linux
+ runtime::Window <|-- Window_windows
+
+ class Window_darwin {
+ //go:build darwin
+ +NSWindow* ptr
+ }
+ class Window_linux {
+ //go:build linux
+ +GtkWindow* ptr
+ }
+ class Window_windows {
+ //go:build windows
+ +HWND ptr
+ }
+
+ note for runtime::Window "Shared interface\nin pkg/application"
+ note for Window_darwin "Objective-C (Cgo)"
+ note for Window_linux "Pure Go GTK calls"
+ note for Window_windows "Win32 API via syscall"
+
+
+Every feature follows this pattern:
+
+1. **Common interface** in `pkg/application`
+2. **Message processor** entry in `pkg/application/messageprocessor_*.go`
+3. **Implementation** per OS under `internal/runtime/*.go` guarded by build tags
+
+Missing functionality on an OS should return `ErrCapability` and register
+availability via `internal/capabilities`.
+
+---
+
+## Summary
+
+These diagrams outline **where the code lives**, **how data moves**, and
+**which layers own which responsibilities**.
+Keep them handy while exploring the detailed pages that follow – they are your
+map to the Wails v3 source tree.
diff --git a/docs/src/content/docs/contributing/index.mdx b/docs/src/content/docs/contributing/index.mdx
index b36e905b7..a6dc96497 100644
--- a/docs/src/content/docs/contributing/index.mdx
+++ b/docs/src/content/docs/contributing/index.mdx
@@ -6,6 +6,7 @@ sidebar:
---
import { Card, CardGrid } from "@astrojs/starlight/components";
+import Mermaid from "../../components/Mermaid.astro";
## Welcome to the Wails v3 Technical Documentation
@@ -47,6 +48,59 @@ context you need.
---
+## Architectural Overview
+
+
+```mermaid
+flowchart TD
+ subgraph Developer Environment
+ CLI[wails3 CLI
Init · Dev · Build · Package]
+ end
+
+ subgraph Build-Time
+ GEN[Binding System
(Static Analysis & Codegen)]
+ ASSET[Asset Server
(Dev Proxy · Embed FS)]
+ PKG[Build & Packaging
Pipeline]
+ end
+
+ subgraph Runtime
+ RUNTIME[Desktop Runtime
(Window · Events · Dialogs)]
+ BIND[Bridge
(Message Processor)]
+ end
+
+ subgraph Application
+ GO[Go Backend
(App Logic)]
+ WEB[Web Frontend
(React/Vue/...)]
+ end
+
+ %% Relationships
+ CLI --> |"generate"| GEN
+ CLI --> |"dev / build"| ASSET
+ CLI --> |"compile & package"| PKG
+
+ GEN --> |"Code Stubs + TS"| GO
+ GEN --> |"Bindings JSON"| WEB
+
+ PKG --> |"Final Binary + Installer"| GO
+
+ GO --> |"Function Calls"| BIND
+ WEB --> |"Invoke / Events"| BIND
+
+ RUNTIME <-->|native messages| BIND
+ RUNTIME --> |"Display Assets"| ASSET
+ WEB <-->|HTTP / In-Memory| ASSET
+```
+
+
+The diagram shows the **end-to-end flow**:
+
+1. **CLI** drives generation, dev server, compilation, and packaging.
+2. **Binding System** produces glue code that lets the **Web Frontend** call into the **Go Backend**.
+3. During development the **Asset Server** proxies to the framework dev server; in production it serves embedded files.
+4. At runtime the **Desktop Runtime** manages windows and OS APIs, while the **Bridge** shuttles messages between Go and JavaScript.
+
+---
+
## What This Documentation Covers
| Topic | Why It Matters |
diff --git a/docs/src/content/docs/guides/msix-packaging.mdx b/docs/src/content/docs/guides/msix-packaging.mdx
new file mode 100644
index 000000000..971bd271e
--- /dev/null
+++ b/docs/src/content/docs/guides/msix-packaging.mdx
@@ -0,0 +1,133 @@
+# MSIX Packaging (Windows)
+
+Wails v3 can generate modern **MSIX** installers for Windows applications, providing a cleaner, safer and Store-ready alternative to traditional **NSIS** or plain `.exe` bundles.
+
+This guide walks through:
+
+* Prerequisites & tool installation
+* Building your app as an MSIX package
+* Signing the package
+* Command-line reference
+* Troubleshooting
+
+---
+
+## 1. Prerequisites
+
+| Requirement | Notes |
+|-------------|-------|
+| **Windows 10 1809+ / Windows 11** | MSIX is only supported on Windows. |
+| **Windows SDK** (for `MakeAppx.exe` & `signtool.exe`) | Install from the [Windows SDK download page](https://developer.microsoft.com/windows/downloads/windows-sdk/). |
+| **Microsoft MSIX Packaging Tool** (optional) | Available from the Microsoft Store – provides a GUI & CLI. |
+| **Code-signing certificate** (recommended) | A `.pfx` file generated by your CA or `New-SelfSignedCertificate`. |
+
+> **Tip:** Wails ships a Task that opens the download pages for you:
+
+```bash
+# installs MakeAppx / signtool (via Windows SDK) and the MSIX Packaging Tool
+wails3 task windows:install:msix:tools
+```
+
+---
+
+## 2. Building an MSIX package
+
+### 2.1 Quick CLI
+
+```bash
+# Production build + MSIX
+wails3 tool msix \
+ --name MyApp \ # executable name
+ --executable build/bin/MyApp.exe \
+ --arch x64 \ # x64, x86 or arm64
+ --out build/bin/MyApp-x64.msix
+```
+
+The command will:
+
+1. Create a temporary layout (`AppxManifest.xml`, `Assets/`).
+2. Call **MakeAppx.exe** (default) or **MsixPackagingTool.exe** if `--use-msix-tool` is passed.
+3. Optionally sign the package (see §3).
+
+### 2.2 Using the generated Taskfile
+
+When you ran `wails init`, Wails created `build/windows/Taskfile.yml`.
+Packaging with MSIX is a one-liner:
+
+```bash
+# default=nsis, override FORMAT
+wails3 task windows:package FORMAT=msix
+```
+
+Output goes to `build/bin/MyApp-.msix`.
+
+---
+
+## 3. Signing the package
+
+Windows will refuse unsigned MSIX packages unless you enable developer-mode, so signing is strongly recommended.
+
+```bash
+wails3 tool msix \
+ --cert build/cert/CodeSign.pfx \
+ --cert-password "pfx-password" \
+ --publisher "CN=MyCompany" \
+ --out build/bin/MyApp.msix
+```
+
+* If you pass `--cert`, Wails automatically runs `signtool sign …`.
+* `--publisher` sets the `Publisher` field inside `AppxManifest.xml`.
+ It **must** match the subject of your certificate.
+
+---
+
+## 4. Command-line reference
+
+| Flag | Default | Description |
+|------|---------|-------------|
+| `--config` | `wails.json` | Project config with **Info** & `fileAssociations`. |
+| `--name` | — | Executable name inside the package (no spaces). |
+| `--executable` | — | Path to the built `.exe`. |
+| `--arch` | `x64` | `x64`, `x86`, or `arm64`. |
+| `--out` | `.msix` | Output path / filename. |
+| `--publisher` | `CN=` | Publisher string in the manifest. |
+| `--cert` | ― | Path to `.pfx` certificate for signing. |
+| `--cert-password` | ― | Password for the `.pfx`. |
+| `--use-msix-tool` | `false` | Use **MsixPackagingTool.exe** instead of **MakeAppx.exe**. |
+| `--use-makeappx` | `true` | Force MakeAppx even if the MSIX Tool is installed. |
+
+---
+
+## 5. File associations
+
+Wails automatically injects file associations declared in `wails.json` into the package manifest:
+
+```json
+"fileAssociations": [
+ { "ext": "wails", "name": "Wails Project", "description": "Wails file", "role": "Editor" }
+]
+```
+
+After installation, Windows will offer your app as a handler for these extensions.
+
+---
+
+## 6. Troubleshooting
+
+| Problem | Solution |
+|---------|----------|
+| `MakeAppx.exe not found` | Install the Windows SDK and restart the terminal. |
+| `signtool.exe not found` | Same as above – both live in the SDK’s *bin* folder. |
+| *Package cannot be installed because publisher mismatch* | The certificate subject (CN) must match `--publisher`. |
+| *The certificate is not trusted* | Import the certificate into **Trusted Root Certification Authorities** or use a publicly trusted code-signing cert. |
+| Need GUI | Install **MSIX Packaging Tool** from the store and run `MsixPackagingTool.exe`. The template generated by Wails is fully compatible. |
+
+---
+
+## 7. Next steps
+
+* [Windows Installer (NSIS) guide](./windows-installer.mdx) – legacy format.
+* [Cross-platform update mechanism](../updates.mdx) – coming soon.
+* Join the community on Discord to share feedback!
+
+Happy packaging!
diff --git a/docs/src/stylesheets/extra.css b/docs/src/stylesheets/extra.css
index 690c38cab..47e0c5386 100644
--- a/docs/src/stylesheets/extra.css
+++ b/docs/src/stylesheets/extra.css
@@ -1,4 +1,6 @@
+@import './mermaid.css';
+
html {
scrollbar-gutter: stable;
overflow-y: scroll; /* Show vertical scrollbar */
-}
\ No newline at end of file
+}
diff --git a/docs/src/stylesheets/mermaid.css b/docs/src/stylesheets/mermaid.css
new file mode 100644
index 000000000..4818131fa
--- /dev/null
+++ b/docs/src/stylesheets/mermaid.css
@@ -0,0 +1,108 @@
+/* Mermaid diagram styling for Starlight */
+
+/* Container for the whole diagram component */
+figure.expandable-diagram {
+ margin: 2rem 0;
+ padding: 1rem;
+ border-radius: 0.5rem;
+ background-color: var(--sl-color-gray-6);
+ box-shadow: var(--sl-shadow-sm);
+}
+
+/* Dark mode adjustments */
+:root[data-theme="dark"] figure.expandable-diagram {
+ background-color: var(--sl-color-gray-1);
+}
+
+/* Title for the diagram */
+figure.expandable-diagram figcaption {
+ font-weight: bold;
+ font-size: 1.1rem;
+ margin-bottom: 1rem;
+ color: var(--sl-color-text);
+}
+
+/* Container for the actual diagram */
+.diagram-content {
+ display: flex;
+ justify-content: center;
+ margin: 1rem 0;
+ min-height: 100px;
+ overflow-x: auto;
+}
+
+/* The diagram itself */
+.mermaid {
+ background-color: var(--sl-color-white);
+ padding: 1rem;
+ border-radius: 0.375rem;
+ max-width: 100%;
+}
+
+:root[data-theme="dark"] .mermaid {
+ background-color: var(--sl-color-black);
+}
+
+/* Source code details element */
+figure.expandable-diagram details {
+ margin-top: 1rem;
+ border-top: 1px solid var(--sl-color-gray-5);
+ padding-top: 0.5rem;
+}
+
+:root[data-theme="dark"] figure.expandable-diagram details {
+ border-top-color: var(--sl-color-gray-3);
+}
+
+/* Source button */
+figure.expandable-diagram summary {
+ cursor: pointer;
+ color: var(--sl-color-text-accent);
+ font-weight: 500;
+ display: inline-block;
+ padding: 0.25rem 0.5rem;
+ border-radius: 0.25rem;
+}
+
+figure.expandable-diagram summary:hover {
+ background-color: var(--sl-color-gray-5);
+}
+
+:root[data-theme="dark"] figure.expandable-diagram summary:hover {
+ background-color: var(--sl-color-gray-2);
+}
+
+/* Source code */
+figure.expandable-diagram details pre {
+ margin-top: 0.5rem;
+ padding: 0.75rem;
+ border-radius: 0.375rem;
+ background-color: var(--sl-color-gray-7);
+ overflow-x: auto;
+}
+
+:root[data-theme="dark"] figure.expandable-diagram details pre {
+ background-color: var(--sl-color-gray-0);
+}
+
+/* Mermaid diagram specific adjustments */
+.mermaid .label {
+ font-family: var(--sl-font);
+ font-size: 0.9rem;
+}
+
+/* Fix for diagram text in dark mode */
+:root[data-theme="dark"] .mermaid text {
+ fill: var(--sl-color-white);
+}
+
+/* Ensure diagrams are responsive */
+@media (max-width: 768px) {
+ .diagram-content {
+ overflow-x: auto;
+ }
+
+ .mermaid {
+ min-width: 100%;
+ }
+}
diff --git a/v3/internal/commands/build_assets/windows/Taskfile.yml b/v3/internal/commands/build_assets/windows/Taskfile.yml
index 534f4fb31..19f137616 100644
--- a/v3/internal/commands/build_assets/windows/Taskfile.yml
+++ b/v3/internal/commands/build_assets/windows/Taskfile.yml
@@ -31,9 +31,16 @@ tasks:
PRODUCTION: '{{.PRODUCTION | default "false"}}'
package:
- summary: Packages a production build of the application into a `.exe` bundle
+ summary: Packages a production build of the application
cmds:
- - task: create:nsis:installer
+ - |-
+ if [ "{{.FORMAT | default "nsis"}}" = "msix" ]; then
+ task: create:msix:package
+ else
+ task: create:nsis:installer
+ fi
+ vars:
+ FORMAT: '{{.FORMAT | default "nsis"}}'
generate:syso:
summary: Generates Windows `.syso` file
@@ -58,6 +65,34 @@ tasks:
ARCH: '{{.ARCH | default ARCH}}'
ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}'
+ create:msix:package:
+ summary: Creates an MSIX package
+ deps:
+ - task: build
+ vars:
+ PRODUCTION: "true"
+ cmds:
+ - |-
+ wails3 tool msix \
+ --config "{{.ROOT_DIR}}/wails.json" \
+ --name "{{.APP_NAME}}" \
+ --executable "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" \
+ --arch "{{.ARCH}}" \
+ --out "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}-{{.ARCH}}.msix" \
+ {{if .CERT_PATH}}--cert "{{.CERT_PATH}}"{{end}} \
+ {{if .PUBLISHER}}--publisher "{{.PUBLISHER}}"{{end}} \
+ {{if .USE_MSIX_TOOL}}--use-msix-tool{{else}}--use-makeappx{{end}}
+ vars:
+ ARCH: '{{.ARCH | default ARCH}}'
+ CERT_PATH: '{{.CERT_PATH | default ""}}'
+ PUBLISHER: '{{.PUBLISHER | default ""}}'
+ USE_MSIX_TOOL: '{{.USE_MSIX_TOOL | default "false"}}'
+
+ install:msix:tools:
+ summary: Installs tools required for MSIX packaging
+ cmds:
+ - wails3 tool msix-install-tools
+
run:
cmds:
- '{{.BIN_DIR}}/{{.APP_NAME}}.exe'
diff --git a/v3/internal/commands/build_assets/windows/msix/app_manifest.xml.tmpl b/v3/internal/commands/build_assets/windows/msix/app_manifest.xml.tmpl
new file mode 100644
index 000000000..7697b318f
--- /dev/null
+++ b/v3/internal/commands/build_assets/windows/msix/app_manifest.xml.tmpl
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+ {{.Info.ProductName}}
+ {{.Info.CompanyName}}
+ {{.Info.Description}}
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{if .FileAssociations}}
+
+
+ {{.Info.ProductName}}
+ Assets\FileIcon.png
+ {{.Info.ProductName}} File
+
+ {{range .FileAssociations}}
+ .{{.Ext}}
+ {{end}}
+
+
+
+ {{end}}
+
+
+
+
+
+
+ {{if .FileAssociations}}
+
+ {{end}}
+
+
diff --git a/v3/internal/commands/build_assets/windows/msix/template.xml.tmpl b/v3/internal/commands/build_assets/windows/msix/template.xml.tmpl
new file mode 100644
index 000000000..69b57bdf0
--- /dev/null
+++ b/v3/internal/commands/build_assets/windows/msix/template.xml.tmpl
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+ {{if .FileAssociations}}
+
+ {{end}}
+
+
+
+ {{if .FileAssociations}}
+
+
+
+ {{range .FileAssociations}}
+
+ .{{.Ext}}
+
+ {{end}}
+
+
+
+ {{end}}
+
+
+
+
+
+
+
+
+
+ false
+ {{.Info.ProductName}}
+ {{.Info.CompanyName}}
+ {{.Info.Description}}
+ Assets\AppIcon.png
+
+
+
+
+ {{.CertificatePath}}
+
+
diff --git a/v3/internal/commands/msix.go b/v3/internal/commands/msix.go
new file mode 100644
index 000000000..457165fc7
--- /dev/null
+++ b/v3/internal/commands/msix.go
@@ -0,0 +1,488 @@
+package commands
+
+import (
+ "embed"
+ "encoding/json"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "text/template"
+
+ "github.com/leaanthony/gosod"
+ "github.com/wailsapp/wails/v3/internal/flags"
+ "github.com/wailsapp/wails/v3/internal/s"
+)
+
+//go:embed build_assets/windows/msix/*
+var msixAssets embed.FS
+
+// MSIXOptions represents the configuration for MSIX packaging
+type MSIXOptions struct {
+ // Info from project config
+ Info struct {
+ CompanyName string `json:"companyName"`
+ ProductName string `json:"productName"`
+ ProductVersion string `json:"version"`
+ ProductIdentifier string `json:"productIdentifier"`
+ Description string `json:"description"`
+ Copyright string `json:"copyright"`
+ Comments string `json:"comments"`
+ }
+ // File associations
+ FileAssociations []struct {
+ Ext string `json:"ext"`
+ Name string `json:"name"`
+ Description string `json:"description"`
+ IconName string `json:"iconName"`
+ Role string `json:"role"`
+ MimeType string `json:"mimeType,omitempty"`
+ } `json:"fileAssociations"`
+ // MSIX specific options
+ Publisher string `json:"publisher"`
+ CertificatePath string `json:"certificatePath"`
+ CertificatePassword string `json:"certificatePassword,omitempty"`
+ ProcessorArchitecture string `json:"processorArchitecture"`
+ ExecutableName string `json:"executableName"`
+ ExecutablePath string `json:"executablePath"`
+ OutputPath string `json:"outputPath"`
+ UseMsixPackagingTool bool `json:"useMsixPackagingTool"`
+ UseMakeAppx bool `json:"useMakeAppx"`
+}
+
+// ToolMSIX creates an MSIX package for Windows applications
+func ToolMSIX(options *flags.ToolMSIX) error {
+ DisableFooter = true
+
+ if runtime.GOOS != "windows" {
+ return fmt.Errorf("MSIX packaging is only supported on Windows")
+ }
+
+ // Check if required tools are installed
+ if err := checkMSIXTools(options); err != nil {
+ return err
+ }
+
+ // Load project configuration
+ configPath := options.ConfigPath
+ if configPath == "" {
+ configPath = "wails.json"
+ }
+
+ // Read the config file
+ configData, err := os.ReadFile(configPath)
+ if err != nil {
+ return fmt.Errorf("error reading config file: %w", err)
+ }
+
+ // Parse the config
+ var config struct {
+ Info map[string]interface{} `json:"info"`
+ FileAssociations []map[string]interface{} `json:"fileAssociations"`
+ }
+ if err := json.Unmarshal(configData, &config); err != nil {
+ return fmt.Errorf("error parsing config file: %w", err)
+ }
+
+ // Create MSIX options
+ msixOptions := MSIXOptions{
+ Publisher: options.Publisher,
+ CertificatePath: options.CertificatePath,
+ CertificatePassword: options.CertificatePassword,
+ ProcessorArchitecture: options.Arch,
+ ExecutableName: options.ExecutableName,
+ ExecutablePath: options.ExecutablePath,
+ OutputPath: options.OutputPath,
+ UseMsixPackagingTool: options.UseMsixPackagingTool,
+ UseMakeAppx: options.UseMakeAppx,
+ }
+
+ // Copy info from config
+ infoBytes, err := json.Marshal(config.Info)
+ if err != nil {
+ return fmt.Errorf("error marshaling info: %w", err)
+ }
+ if err := json.Unmarshal(infoBytes, &msixOptions.Info); err != nil {
+ return fmt.Errorf("error unmarshaling info: %w", err)
+ }
+
+ // Copy file associations from config
+ if len(config.FileAssociations) > 0 {
+ faBytes, err := json.Marshal(config.FileAssociations)
+ if err != nil {
+ return fmt.Errorf("error marshaling file associations: %w", err)
+ }
+ if err := json.Unmarshal(faBytes, &msixOptions.FileAssociations); err != nil {
+ return fmt.Errorf("error unmarshaling file associations: %w", err)
+ }
+ }
+
+ // Validate options
+ if err := validateMSIXOptions(&msixOptions); err != nil {
+ return err
+ }
+
+ // Create MSIX package
+ if msixOptions.UseMsixPackagingTool {
+ return createMSIXWithPackagingTool(&msixOptions)
+ } else if msixOptions.UseMakeAppx {
+ return createMSIXWithMakeAppx(&msixOptions)
+ }
+
+ // Default to MakeAppx if neither is specified
+ return createMSIXWithMakeAppx(&msixOptions)
+}
+
+// checkMSIXTools checks if the required tools for MSIX packaging are installed
+func checkMSIXTools(options *flags.ToolMSIX) error {
+ // Check if MsixPackagingTool is installed if requested
+ if options.UseMsixPackagingTool {
+ cmd := exec.Command("powershell", "-Command", "Get-AppxPackage -Name Microsoft.MsixPackagingTool")
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("Microsoft MSIX Packaging Tool is not installed. Please install it from the Microsoft Store")
+ }
+ }
+
+ // Check if MakeAppx is available if requested
+ if options.UseMakeAppx {
+ cmd := exec.Command("where", "MakeAppx.exe")
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("MakeAppx.exe is not found in PATH. Please install the Windows SDK")
+ }
+ }
+
+ // If neither is specified, check for MakeAppx as the default
+ if !options.UseMsixPackagingTool && !options.UseMakeAppx {
+ cmd := exec.Command("where", "MakeAppx.exe")
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("MakeAppx.exe is not found in PATH. Please install the Windows SDK")
+ }
+ }
+
+ // Check if signtool is available for signing
+ if options.CertificatePath != "" {
+ cmd := exec.Command("where", "signtool.exe")
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("signtool.exe is not found in PATH. Please install the Windows SDK")
+ }
+ }
+
+ return nil
+}
+
+// validateMSIXOptions validates the MSIX options
+func validateMSIXOptions(options *MSIXOptions) error {
+ // Check required fields
+ if options.Info.ProductName == "" {
+ return fmt.Errorf("product name is required")
+ }
+ if options.Info.ProductIdentifier == "" {
+ return fmt.Errorf("product identifier is required")
+ }
+ if options.Info.CompanyName == "" {
+ return fmt.Errorf("company name is required")
+ }
+ if options.ExecutableName == "" {
+ return fmt.Errorf("executable name is required")
+ }
+ if options.ExecutablePath == "" {
+ return fmt.Errorf("executable path is required")
+ }
+
+ // Validate executable path
+ if _, err := os.Stat(options.ExecutablePath); os.IsNotExist(err) {
+ return fmt.Errorf("executable file not found: %s", options.ExecutablePath)
+ }
+
+ // Validate certificate path if provided
+ if options.CertificatePath != "" {
+ if _, err := os.Stat(options.CertificatePath); os.IsNotExist(err) {
+ return fmt.Errorf("certificate file not found: %s", options.CertificatePath)
+ }
+ }
+
+ // Set default processor architecture if not provided
+ if options.ProcessorArchitecture == "" {
+ options.ProcessorArchitecture = "x64"
+ }
+
+ // Set default publisher if not provided
+ if options.Publisher == "" {
+ options.Publisher = fmt.Sprintf("CN=%s", options.Info.CompanyName)
+ }
+
+ // Set default output path if not provided
+ if options.OutputPath == "" {
+ options.OutputPath = filepath.Join(".", fmt.Sprintf("%s.msix", options.Info.ProductName))
+ }
+
+ return nil
+}
+
+// createMSIXWithPackagingTool creates an MSIX package using the Microsoft MSIX Packaging Tool
+func createMSIXWithPackagingTool(options *MSIXOptions) error {
+ // Create a temporary directory for the template
+ tempDir, err := os.MkdirTemp("", "wails-msix-")
+ if err != nil {
+ return fmt.Errorf("error creating temporary directory: %w", err)
+ }
+ defer os.RemoveAll(tempDir)
+
+ // Generate the template file
+ templatePath := filepath.Join(tempDir, "template.xml")
+ if err := generateMSIXTemplate(options, templatePath); err != nil {
+ return fmt.Errorf("error generating MSIX template: %w", err)
+ }
+
+ // Create the MSIX package
+ fmt.Println("Creating MSIX package using Microsoft MSIX Packaging Tool...")
+ args := []string{"create-package", "--template", templatePath}
+
+ // Add certificate password if provided
+ if options.CertificatePassword != "" {
+ args = append(args, "--certPassword", options.CertificatePassword)
+ }
+
+ cmd := exec.Command("MsixPackagingTool.exe", args...)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("error creating MSIX package: %w", err)
+ }
+
+ fmt.Printf("MSIX package created successfully: %s\n", options.OutputPath)
+ return nil
+}
+
+// createMSIXWithMakeAppx creates an MSIX package using MakeAppx.exe
+func createMSIXWithMakeAppx(options *MSIXOptions) error {
+ // Create a temporary directory for the package structure
+ tempDir, err := os.MkdirTemp("", "wails-msix-")
+ if err != nil {
+ return fmt.Errorf("error creating temporary directory: %w", err)
+ }
+ defer os.RemoveAll(tempDir)
+
+ // Create the package structure
+ if err := createMSIXPackageStructure(options, tempDir); err != nil {
+ return fmt.Errorf("error creating MSIX package structure: %w", err)
+ }
+
+ // Create the MSIX package
+ fmt.Println("Creating MSIX package using MakeAppx.exe...")
+ cmd := exec.Command("MakeAppx.exe", "pack", "/d", tempDir, "/p", options.OutputPath)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("error creating MSIX package: %w", err)
+ }
+
+ // Sign the package if certificate is provided
+ if options.CertificatePath != "" {
+ fmt.Println("Signing MSIX package...")
+ signArgs := []string{"sign", "/fd", "SHA256", "/a", "/f", options.CertificatePath}
+
+ // Add certificate password if provided
+ if options.CertificatePassword != "" {
+ signArgs = append(signArgs, "/p", options.CertificatePassword)
+ }
+
+ signArgs = append(signArgs, options.OutputPath)
+
+ cmd = exec.Command("signtool.exe", signArgs...)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("error signing MSIX package: %w", err)
+ }
+ }
+
+ fmt.Printf("MSIX package created successfully: %s\n", options.OutputPath)
+ return nil
+}
+
+// generateMSIXTemplate generates the MSIX template file for the Microsoft MSIX Packaging Tool
+func generateMSIXTemplate(options *MSIXOptions, outputPath string) error {
+ // Read the template file
+ templateData, err := msixAssets.ReadFile("build_assets/windows/msix/template.xml.tmpl")
+ if err != nil {
+ return fmt.Errorf("error reading template file: %w", err)
+ }
+
+ // Parse the template
+ tmpl, err := template.New("msix-template").Parse(string(templateData))
+ if err != nil {
+ return fmt.Errorf("error parsing template: %w", err)
+ }
+
+ // Create the output file
+ file, err := os.Create(outputPath)
+ if err != nil {
+ return fmt.Errorf("error creating output file: %w", err)
+ }
+ defer file.Close()
+
+ // Execute the template
+ if err := tmpl.Execute(file, options); err != nil {
+ return fmt.Errorf("error executing template: %w", err)
+ }
+
+ return nil
+}
+
+// createMSIXPackageStructure creates the MSIX package structure for MakeAppx.exe
+func createMSIXPackageStructure(options *MSIXOptions, outputDir string) error {
+ // Create the Assets directory
+ assetsDir := filepath.Join(outputDir, "Assets")
+ if err := os.MkdirAll(assetsDir, 0755); err != nil {
+ return fmt.Errorf("error creating Assets directory: %w", err)
+ }
+
+ // Generate the AppxManifest.xml file
+ manifestPath := filepath.Join(outputDir, "AppxManifest.xml")
+ if err := generateAppxManifest(options, manifestPath); err != nil {
+ return fmt.Errorf("error generating AppxManifest.xml: %w", err)
+ }
+
+ // Copy the executable
+ executableDest := filepath.Join(outputDir, filepath.Base(options.ExecutablePath))
+ if err := copyFile(options.ExecutablePath, executableDest); err != nil {
+ return fmt.Errorf("error copying executable: %w", err)
+ }
+
+ // Copy any additional files needed for the application
+ // This would include DLLs, resources, etc.
+ // For now, we'll just copy the executable
+
+ // Generate placeholder assets
+ assets := []string{
+ "Square150x150Logo.png",
+ "Square44x44Logo.png",
+ "Wide310x150Logo.png",
+ "SplashScreen.png",
+ "StoreLogo.png",
+ }
+
+ // Add FileIcon.png if there are file associations
+ if len(options.FileAssociations) > 0 {
+ assets = append(assets, "FileIcon.png")
+ }
+
+ // Generate placeholder assets
+ for _, asset := range assets {
+ assetPath := filepath.Join(assetsDir, asset)
+ if err := generatePlaceholderImage(assetPath); err != nil {
+ return fmt.Errorf("error generating placeholder image %s: %w", asset, err)
+ }
+ }
+
+ return nil
+}
+
+// generateAppxManifest generates the AppxManifest.xml file
+func generateAppxManifest(options *MSIXOptions, outputPath string) error {
+ // Read the template file
+ templateData, err := msixAssets.ReadFile("build_assets/windows/msix/app_manifest.xml.tmpl")
+ if err != nil {
+ return fmt.Errorf("error reading template file: %w", err)
+ }
+
+ // Parse the template
+ tmpl, err := template.New("appx-manifest").Parse(string(templateData))
+ if err != nil {
+ return fmt.Errorf("error parsing template: %w", err)
+ }
+
+ // Create the output file
+ file, err := os.Create(outputPath)
+ if err != nil {
+ return fmt.Errorf("error creating output file: %w", err)
+ }
+ defer file.Close()
+
+ // Execute the template
+ if err := tmpl.Execute(file, options); err != nil {
+ return fmt.Errorf("error executing template: %w", err)
+ }
+
+ return nil
+}
+
+// generatePlaceholderImage generates a placeholder image file
+func generatePlaceholderImage(outputPath string) error {
+ // For now, we'll create a simple 1x1 transparent PNG
+ // In a real implementation, we would generate proper icons based on the application icon
+
+ // Create a minimal valid PNG file (1x1 transparent pixel)
+ pngData := []byte{
+ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D,
+ 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, 0x89, 0x00, 0x00, 0x00,
+ 0x0A, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00,
+ 0x05, 0x00, 0x01, 0x0D, 0x0A, 0x2D, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x49,
+ 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82,
+ }
+
+ return os.WriteFile(outputPath, pngData, 0644)
+}
+
+// copyFile copies a file from src to dst
+func copyFile(src, dst string) error {
+ // Read the source file
+ data, err := os.ReadFile(src)
+ if err != nil {
+ return err
+ }
+
+ // Write the destination file
+ return os.WriteFile(dst, data, 0644)
+}
+
+// InstallMSIXTools installs the required tools for MSIX packaging
+func InstallMSIXTools() error {
+ // Check if running on Windows
+ if runtime.GOOS != "windows" {
+ return fmt.Errorf("MSIX packaging is only supported on Windows")
+ }
+
+ fmt.Println("Installing MSIX packaging tools...")
+
+ // Install MSIX Packaging Tool from Microsoft Store
+ fmt.Println("Installing Microsoft MSIX Packaging Tool from Microsoft Store...")
+ cmd := exec.Command("powershell", "-Command", "Start-Process ms-windows-store://pdp/?ProductId=9N5R1TQPJVBP")
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("error launching Microsoft Store: %w", err)
+ }
+
+ // Check if Windows SDK is installed
+ fmt.Println("Checking for Windows SDK...")
+ sdkInstalled := false
+ cmd = exec.Command("where", "MakeAppx.exe")
+ if err := cmd.Run(); err == nil {
+ sdkInstalled = true
+ fmt.Println("Windows SDK is already installed.")
+ }
+
+ // Install Windows SDK if not installed
+ if !sdkInstalled {
+ fmt.Println("Windows SDK is not installed. Please download and install from:")
+ fmt.Println("https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/")
+
+ // Open the download page
+ cmd = exec.Command("powershell", "-Command", "Start-Process https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/")
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("error opening Windows SDK download page: %w", err)
+ }
+ }
+
+ fmt.Println("MSIX packaging tools installation initiated. Please complete the installation process in the opened windows.")
+ return nil
+}
+
+// init registers the MSIX command
+func init() {
+ // Register the MSIX command in the CLI
+ // This will be called by the CLI framework
+}
diff --git a/v3/internal/commands/tool_package.go b/v3/internal/commands/tool_package.go
index 2bf2721a6..208f5e000 100644
--- a/v3/internal/commands/tool_package.go
+++ b/v3/internal/commands/tool_package.go
@@ -4,17 +4,23 @@ import (
"fmt"
"os"
"path/filepath"
+ "runtime"
"strings"
+ "github.com/wailsapp/wails/v3/internal/commands/dmg"
"github.com/wailsapp/wails/v3/internal/flags"
"github.com/wailsapp/wails/v3/internal/packager"
)
-// ToolPackage generates a Linux package in the specified format using nfpm
+// ToolPackage generates a package in the specified format
func ToolPackage(options *flags.ToolPackage) error {
DisableFooter = true
- if options.ConfigPath == "" {
+ // Check if we're creating a DMG
+ isDMG := strings.ToLower(options.Format) == "dmg" || options.CreateDMG
+
+ // Config file is required for Linux packages but optional for DMG
+ if options.ConfigPath == "" && !isDMG {
return fmt.Errorf("please provide a config file using the -config flag")
}
@@ -22,7 +28,48 @@ func ToolPackage(options *flags.ToolPackage) error {
return fmt.Errorf("please provide an executable name using the -name flag")
}
- // Validate format
+ // Handle DMG creation for macOS
+ if isDMG {
+ if runtime.GOOS != "darwin" {
+ return fmt.Errorf("DMG creation is only supported on macOS")
+ }
+
+ // For DMG, we expect the .app bundle to already exist
+ appPath := filepath.Join(options.Out, fmt.Sprintf("%s.app", options.ExecutableName))
+ if _, err := os.Stat(appPath); os.IsNotExist(err) {
+ return fmt.Errorf("application bundle not found: %s", appPath)
+ }
+
+ // Create output path for DMG
+ dmgPath := filepath.Join(options.Out, fmt.Sprintf("%s.dmg", options.ExecutableName))
+
+ // Create DMG creator
+ dmgCreator, err := dmg.New(appPath, dmgPath, options.ExecutableName)
+ if err != nil {
+ return fmt.Errorf("error creating DMG: %w", err)
+ }
+
+ // Set background image if provided
+ if options.BackgroundImage != "" {
+ if err := dmgCreator.SetBackgroundImage(options.BackgroundImage); err != nil {
+ return fmt.Errorf("error setting background image: %w", err)
+ }
+ }
+
+ // Set default icon positions
+ dmgCreator.AddIconPosition(filepath.Base(appPath), 150, 175)
+ dmgCreator.AddIconPosition("Applications", 450, 175)
+
+ // Create the DMG
+ if err := dmgCreator.Create(); err != nil {
+ return fmt.Errorf("error creating DMG: %w", err)
+ }
+
+ fmt.Printf("DMG created successfully: %s\n", dmgPath)
+ return nil
+ }
+
+ // For Linux packages, continue with existing logic
var pkgType packager.PackageType
switch strings.ToLower(options.Format) {
case "deb":
@@ -32,7 +79,7 @@ func ToolPackage(options *flags.ToolPackage) error {
case "archlinux":
pkgType = packager.ARCH
default:
- return fmt.Errorf("unsupported package format '%s'. Supported formats: deb, rpm, archlinux", options.Format)
+ return fmt.Errorf("unsupported package format '%s'. Supported formats: deb, rpm, archlinux, dmg", options.Format)
}
// Get absolute path of config file
diff --git a/v3/internal/doctor/doctor_windows.go b/v3/internal/doctor/doctor_windows.go
index 541041e0a..607b689fa 100644
--- a/v3/internal/doctor/doctor_windows.go
+++ b/v3/internal/doctor/doctor_windows.go
@@ -6,6 +6,7 @@ import (
"github.com/samber/lo"
"github.com/wailsapp/go-webview2/webviewloader"
"os/exec"
+ "strings"
)
func getInfo() (map[string]string, bool) {
@@ -30,8 +31,42 @@ func getNSISVersion() string {
return string(output)
}
+func getMakeAppxVersion() string {
+ // Check if MakeAppx.exe is available (part of Windows SDK)
+ _, err := exec.LookPath("MakeAppx.exe")
+ if err != nil {
+ return "Not Installed"
+ }
+ return "Installed"
+}
+
+func getMSIXPackagingToolVersion() string {
+ // Check if MSIX Packaging Tool is installed
+ // Use PowerShell to check if the app is installed from Microsoft Store
+ cmd := exec.Command("powershell", "-Command", "Get-AppxPackage -Name Microsoft.MsixPackagingTool")
+ output, err := cmd.Output()
+ if err != nil || len(output) == 0 || !strings.Contains(string(output), "Microsoft.MsixPackagingTool") {
+ return "Not Installed"
+ }
+ return "Installed"
+}
+
+func getSignToolVersion() string {
+ // Check if signtool.exe is available (part of Windows SDK)
+ _, err := exec.LookPath("signtool.exe")
+ if err != nil {
+ return "Not Installed"
+ }
+ return "Installed"
+}
+
func checkPlatformDependencies(result map[string]string, ok *bool) {
checkCommonDependencies(result, ok)
// add nsis
result["NSIS"] = getNSISVersion()
+
+ // Add MSIX tooling checks
+ result["MakeAppx.exe (Windows SDK)"] = getMakeAppxVersion()
+ result["MSIX Packaging Tool"] = getMSIXPackagingToolVersion()
+ result["SignTool.exe (Windows SDK)"] = getSignToolVersion()
}
diff --git a/v3/internal/flags/msix.go b/v3/internal/flags/msix.go
new file mode 100644
index 000000000..17bbbd446
--- /dev/null
+++ b/v3/internal/flags/msix.go
@@ -0,0 +1,26 @@
+package flags
+
+// ToolMSIX represents the options for the MSIX packaging command
+type ToolMSIX struct {
+ Common
+
+ // Project configuration
+ ConfigPath string `name:"config" description:"Path to the project configuration file" default:"wails.json"`
+
+ // MSIX package information
+ Publisher string `name:"publisher" description:"Publisher name for the MSIX package (e.g., CN=CompanyName)" default:""`
+
+ // Certificate for signing
+ CertificatePath string `name:"cert" description:"Path to the certificate file for signing the MSIX package" default:""`
+ CertificatePassword string `name:"cert-password" description:"Password for the certificate file" default:""`
+
+ // Build options
+ Arch string `name:"arch" description:"Architecture of the package (x64, x86, arm64)" default:"x64"`
+ ExecutableName string `name:"name" description:"Name of the executable in the package" default:""`
+ ExecutablePath string `name:"executable" description:"Path to the executable file to package" default:""`
+ OutputPath string `name:"out" description:"Path where the MSIX package will be saved" default:""`
+
+ // Tool selection
+ UseMsixPackagingTool bool `name:"use-msix-tool" description:"Use the Microsoft MSIX Packaging Tool for packaging" default:"false"`
+ UseMakeAppx bool `name:"use-makeappx" description:"Use MakeAppx.exe for packaging" default:"true"`
+}
diff --git a/v3/internal/flags/package.go b/v3/internal/flags/package.go
index f9160192b..bd56107c5 100644
--- a/v3/internal/flags/package.go
+++ b/v3/internal/flags/package.go
@@ -4,8 +4,10 @@ package flags
type ToolPackage struct {
Common
- Format string `name:"format" description:"Package format to generate (deb, rpm, archlinux)" default:"deb"`
+ Format string `name:"format" description:"Package format to generate (deb, rpm, archlinux, dmg)" default:"deb"`
ExecutableName string `name:"name" description:"Name of the executable to package" default:"myapp"`
ConfigPath string `name:"config" description:"Path to the package configuration file" default:""`
Out string `name:"out" description:"Path to the output dir" default:"."`
+ BackgroundImage string `name:"background" description:"Path to an optional background image for the DMG" default:""`
+ CreateDMG bool `name:"create-dmg" description:"Create a DMG file (macOS only)" default:"false"`
}