Electron 应用如何上架微软商店:从 MSIX 打包到商店提交

手里有这么一个 Electron 应用,要在 Windows 上分发给最终用户。除了一直以来都在用的 NSIS 安装包、便携版之外,我们还盼着它能出现在微软商店里。说起来原因其实挺现实的:

  1. 可信分发渠道:商店里的应用都是签过名、审过的,用户安装的时候不会再被 SmartScreen 拦,也不必去面对那一句冷冰冰的「未知发行者」。
  2. 自动更新与商业化:更新的事,商店替你接管了;订阅、永久许可证,也都能直接对接上。
  3. 覆盖 Windows 10/11 自带的入口:winget、商店搜索、开始菜单推荐......这些入口,对拉新是实打实有用的。

只是 Electron 它终究不是 UWP。要想上架微软商店,其实核心也就一件事------把 Electron 的产物重新打包成微软商店认得下的 MSIX 包,再老老实实把注册、提交流程走完。听起来轻巧,可真上手了,坑还不少。为了把这些坑填平,我们花了不少功夫把整条链路摸了个通透,下面就把每一步掰开来揉碎了讲。

关于 HagiCode

这篇文章里讲的方案,来自我们在 HagiCode 项目里的实践。HagiCode Desktop 是一个基于 Electron 的桌面端,要同时通过官网、GitHub Release、还有微软商店这三个渠道分发给用户。商店这条渠道是怎么打通的,就是这篇文章要讲的事。文末有更多关于 HagiCode 的信息,要是你感兴趣,不妨拉到底看看。

分析:上架之前必须想清楚的四个问题

上架微软商店这件事,技术链条上有四个关键判断。想清楚了,后面也就不必反复返工了------毕竟谁也不想返工呢。

1. 微软商店只接受 MSIX / AppX,不接受传统 NSIS/EXE

微软商店对桌面应用(Desktop Bridge)的支持,是建立在 MSIX 格式之上的。传统的 NSIS 安装包没法直接提交,得先用 MakeAppx 重新打包成 MSIX。好在 Electron Forge 提供了一个 @electron-forge/maker-msix maker,它能在打包阶段就直接产出 MSIX,省下了从已安装目录反推打包那一通折腾。

我们项目里就挂着这么一个 maker:

复制代码

|--------------------------------------------------------|
| { |
| name: '@electron-forge/maker-msix', |
| platforms: ['win32'], |
| config: { |
| appManifest: msixManifestPath, |
| packageAssets: msixAssetsPath, |
| logLevel: 'warn', |
| ...(windowsKitPath ? { windowsKitPath } : {}), |
| ...(windowsKitVersion ? { windowsKitVersion } : {}), |
| ...msixSigningConfig, |
| }, |
| }, |

关键输入其实就两个:appManifest(也就是 AppxManifest.xml,定义包身份和能力)和 packageAssets(商店图标资产)。这两个要是错了,后面做得再漂亮,也是白搭。

2. 包身份必须提前在合作伙伴中心预留

MSIX 包里那个 Identity 字段(NamePublisher),可不是随便填填就行的,必须和合作伙伴中心里预留的应用身份分毫不差,差一个字符都会被打回来。我们预留的身份,记在 forge.store-config.json 里:

复制代码

|-----------------------------------------------------------------------------------------------------------|
| { |
| "packageIdentity": { |
| "displayName": "Hagicode", |
| "publisherDisplayName": "newbe36524", |
| "publisher": "CN=8B6C8A94-AAE5-4C8B-9202-A29EA42B042F", |
| "identityName": "newbe36524.Hagicode", |
| "backgroundColor": "transparent", |
| "languages": ["en-US", "zh-CN", "zh-TW", "ja-JP", "ko-KR", "de-DE", "fr-FR", "es-ES", "pt-BR", "ru-RU"] |
| } |
| } |

这里头那个 publisher 字符串,是从开发者账号注册之后微软签发的证书主题里来的,必须逐字符匹配。identityName 呢,则是你预留的包名前缀。这个字符串,一定要从合作伙伴中心原样复制下来,千万别自己手敲------这件事我们在后面「常见坑」里还会再唠一遍。

3. 桌面应用必须声明 runFullTrust 能力

Electron 应用要用到完整的文件系统访问、要拉起子进程、还要跑 Node 运行时,这些只能在「完全信任」模式下才能实现。所以 MSIX 清单里,必须老老实实声明 runFullTrust 能力,不然应用一启动,就被沙箱给拦下了,表现出来的,就是各种让人摸不着头脑的崩溃。我们的配置长这样:

复制代码

|---------------------------------------|
| { |
| "msix": { |
| "minVersion": "10.0.17763.0", |
| "maxVersionTested": "10.0.19045.0", |
| "capabilities": [ |
| "runFullTrust", |
| "internetClient", |
| "internetClientServer", |
| "privateNetworkClientsServer" |
| ] |
| } |
| } |

runFullTrust 是桌面应用上架的标配。minVersion 设到 17763(也就是 Windows 10 1809),是因为从这个版本起,MSIX 才稳定支持桌面 Win32 应用,设低了,用户装不上;设高了,又覆盖不到那些上了年纪的老机器。

4. 商店提交需要 Windows 环境 + Microsoft Store CLI

打包这件事,倒是可以在跨平台的 CI 上做,可商店提交(msstore publish)就不行了,必须在 Windows 环境里跑 Microsoft Store CLI,还得配好 Azure AD 应用凭证。这也就是为什么自动化流水线里那个 publish_store 作业,非得跑在 windows-latest runner 上不可。这一点是绕不开的硬约束,不像打包那样可以塞进 Linux 容器里去。

解决:完整上架的八步流程

把上面的分析串起来,要把一个 Electron 应用上架到微软商店,完整的步骤大概是这样的。

步骤 1:注册开发者账号

先到 Partner Center 注册一个开发者账号(个人或者公司都行),把那笔一次性的费用给交了。账号激活之后,你会拿到一个 Publisher 证书主题字符串 ,大概长这样:CN=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX。这,就是后面 publisher 字段唯一的来源。

步骤 2:在商店里预留应用身份

在合作伙伴中心新建一个应用,填上你想保留的名称。系统会分配给你 identityName,再和你自己的 Publisher 一组合,完整的包身份就成型了。把这个身份,原样抄到本地配置里:

复制代码

|-----------------------------------------------------------|
| // forge.store-config.json |
| { |
| "packageIdentity": { |
| "displayName": "Hagicode", |
| "publisherDisplayName": "newbe36524", |
| "publisher": "CN=8B6C8A94-AAE5-4C8B-9202-A29EA42B042F", |
| "identityName": "newbe36524.Hagicode" |
| } |
| } |

步骤 3:准备商店图标资产

微软商店要的是一组固定尺寸的 PNG:StoreLogo.pngSquare44x44Logo.pngSquare150x150Logo.pngWide310x150Logo.png 之类的。我们的 prepare-msix.js 脚本,在打包之前会先校验这些资产是不是都齐了:

复制代码

|--------------------------------------------------------------------------------------------------------------------|
| // 校验商店必需的图标资产,缺一个都不行 |
| const requiredAssets = ['StoreLogo.png', 'Square44x44Logo.png', 'Square150x150Logo.png', 'Wide310x150Logo.png']; |
| for (const assetName of requiredAssets) { |
| const assetPath = path.join(paths.generatedAssetsPath, assetName); |
| if (!fs.existsSync(assetPath)) { |
| throw new Error(`Missing required MSIX asset after preparation: ${assetPath}`); |
| } |
| } |

为什么要这么做呢?因为缺一个尺寸,MakeAppx 打包的时候并不会告诉你具体哪里错了,等到商店审核的时候才被打回来------这时候,你已经等了好几天了。提前校验,是一个非常有效的防御。

步骤 4:生成 AppxManifest.xml

清单里要塞进包身份、能力、可视资产、入口可执行文件。我们用一个覆盖配置(forge.store-config.json)来驱动 prepare-msix.js 生成清单,确保身份和商店对得上。清单里关键的那几段,大概是这样:

复制代码

|---------------------------------------------------------------------------------------------------|
| <!-- 包身份:必须和合作伙伴中心一致 --> |
| <Identity Name="newbe36524.Hagicode" |
| Publisher="CN=8B6C8A94-AAE5-4C8B-9202-A29EA42B042F" |
| Version="1.2.3.0" /> |
| |
| <Applications> |
| <Application Id="Hagicode" Executable="Hagicode.exe" EntryPoint="Windows.FullTrustApplication"> |
| <uap:VisualElements ... /> |
| </Application> |
| </Applications> |
| |
| <!-- 能力声明:runFullTrust 是桌面应用的关键 --> |
| <Capabilities> |
| <rescap:Capability Name="runFullTrust" /> |
| <Capability Name="internetClientServer" /> |
| </Capabilities> |

注意那一行 EntryPoint="Windows.FullTrustApplication",这可是桌面应用的关键标记,配合 runFullTrust 能力,才能以完整的权限跑起来。少了它,应用就只能乖乖待在沙箱里,憋屈得很。

步骤 5:用 maker-msix 打包

构建命令写在 package.json 里:

复制代码

|-----------------------------------------------------------------------------------------------|
| { |
| "scripts": { |
| "build:win:store": "npm run generate:store-bindings && node scripts/build-store-package.js" |
| } |
| } |

它最终调起 Electron Forge,把 forge.store-config.json 当作覆盖配置带进去,maker-msix 会去调用 Windows SDK 的 MakeAppx,吐出 .msix 文件来。这里头有个硬约束:打包必须在 Windows 上做 (或者带 Windows SDK 的容器里),毕竟它依赖 MakeAppx,这点没法绕。

步骤 6:签名(商店提交时可以不签)

这一步,其实很容易被忽略------提交到商店的包,微软会用它自己的证书重新签一遍,所以「正式提交」之外的开发自测阶段,是可以不签名的。只是你要是想在本地装上测试一下,那就得用受信任证书签一下了,不然 Windows 是会拒绝安装的。我们的 resolveMsixSigningConfig 在没配签名材料的时候,会返回一个空对象,让流程接着往下走:

复制代码

|-------------------------------------------------|
| // 没配签名材料就不签,让商店统一重签 |
| function resolveMsixSigningConfig() { |
| if (!process.env.MSIX_CERT_FILE) return {}; |
| return { |
| signMethod: 'signtool', |
| certFilePath: process.env.MSIX_CERT_FILE, |
| certPassword: process.env.MSIX_CERT_PASSWORD, |
| }; |
| } |

把「自测签名」和「提交空签名」这两条路径分开,是一个非常关键的实践。

步骤 7:配置 Microsoft Store CLI 凭证

到 Azure 门户去创建一个 Azure AD 应用,给它授予访问 Partner Center 的权限,然后拿到下面这一组凭证:

  • AZURE_AD_APPLICATION_CLIENT_ID
  • AZURE_AD_APPLICATION_SECRET
  • AZURE_AD_TENANT_ID
  • SELLER_ID(合作伙伴中心的卖家 ID)
  • MICROSOFT_STORE_PRODUCT_ID(预留应用的产品 ID)

这一步稍微有点绕,不过 Azure 门户和 Partner Center 的文档里都写得很细,照着做就是了。

步骤 8:提交到商店

在 Windows 环境里,用 Microsoft Store CLI 提交:

复制代码

|----------------------------------------------------------------------|
| # 配置凭证 |
| msstore reconfigure --tenantId $env:AZURE_AD_TENANT_ID ``` | | --clientId env:AZURE_AD_APPLICATION_CLIENT_ID ``` | | ``--clientSecret env:AZURE_AD_APPLICATION_SECRET ``` |
| --sellerId $env:SELLER_ID |
| |
| # 提交 MSIX 包到预留的产品 |
| msstore publish "$packagePath" -id $env:MICROSOFT_STORE_PRODUCT_ID |

提交完之后,还要回到 Partner Center 把商店详情(描述、截图、定价、分级)填齐,最后点一下提交审核。审核一般要等 1--3 个工作日,第一次提审嘛,总会久一点。

实践:把配置和踩坑经验沉淀下来

相关推荐
XUHUOJUN2 小时前
Windows Server 2025 Stretch Cluster 架构详解
windows·microsoft·azure local
weedsfly2 小时前
单例模式在前端中的正确打开方式
前端·javascript·面试
XUHUOJUN3 小时前
Windows Server 2025 RAC vs Stretch Cluster 技术定位
windows·microsoft·azure local
多加点辣也没关系16 小时前
JavaScript|第4章:类型转换
开发语言·javascript
yqcoder16 小时前
httpOnly 是什么,又有什么用?
开发语言·前端·javascript
烬羽17 小时前
还在手动拼路径、写回调地狱?一文吃透 Node.js 的 path 和 fs
javascript·程序员·node.js
mONESY17 小时前
LangChain JS 高性能并行执行解析、工具调用底层封装
javascript
迅速的自行车18 小时前
从一段模板说起
前端·javascript·vue.js
YIAN18 小时前
LangChain JS 工具调用实战:基于 DeepSeek-V4-Flash 实现本地文件读取 AI 代码助手(完整可运行源码)
javascript·langchain·前端框架