Skip to content

ManualZH

FenTouXunGui edited this page Oct 13, 2026 · 4 revisions

ShinyApps 使用手册

English | 中文

面向两个读者:(A) App 开发者 —— 如何把你开发的 Shiny App 接入 ShinyApps;(B) ShinyApps 维护者 —— 整个系统的运行过程、每个 workflow 的职责、发布与排障。

英文版见 ShinyApps User Manual (EN)。本文档基于 2026-09/10 的实机开发与验收记录整理,并对照当前仓库状态核对。建议放到 GitHub Wiki 时按下方「建议的 Wiki 页面拆分」拆成多页。


建议的 Wiki 页面拆分

Wiki 页面 内容 对应本文档章节
Home 项目简介、架构总览、快速导航 第 0、1 节
用户指南 安装、启动页、安装/运行/更新/卸载、数据位置 第 4 节
开发者指南:接入你的 App bundle 打包、catalog 登记、图标 第 2 节
维护者指南:仓库与工作流 仓库结构、每个 workflow 的触发/产物/职责 第 3 节
发布流程 发壳、发 app、发图标、回滚 第 5 节
故障排查 FAQ 已知问题与解决办法 第 6 节
已知限制与路线图 未解决问题、建议补充 第 7 节
贡献指南 向上游 shinyelectron 提交 PR 第 8 节

语言切换(Language switching)

GitHub Wiki 没有内置多语言,用以下任一种方式即可:

  1. 每个页面顶部放一行(本文档已采用):[English](ManualEN) | **中文**;
  2. 用 _Sidebar.md(推荐):它在所有 Wiki 页面都显示,等于全站语言开关。仓库里已附一份 _Sidebar.md,中文/英文链接都指向 ManualEN / ManualZH。

本文档约定使用纯字母页名:英文页 ManualEN,中文页 ManualZH(不含任何连字符/标点,避免中文输入法把 - 打成全角「‐」)。


0. 这是什么

ShinyApps 是一个 Electron 桌面「外壳 / 启动器」,把多个 R/Shiny 应用聚合到一个启动页里,用户可以按需安装、运行、更新、卸载每个 App。当前可用应用(含描述、版本、支持平台、许可与是否可用)见 在线应用目录(https://fentouxungui.github.io/ShinyApps/),每次发布自动重新生成;机器可读清单为 catalog.json。

它基于开源项目 shinyelectron 的一个分支:fentouxungui/shinyelectron@multi-app-store。

0.1 三层架构

┌────────────────────────────────────────────────────────────┐
│ Tier 0 · 运行时底座(随安装包装一次,常驻,不卸载)        │
│   Electron 壳 + 便携 R 4.6.1 + 最小启动依赖(shiny 等)    │
├────────────────────────────────────────────────────────────┤
│ Tier 1 · 共享依赖池  <userData>/lib                        │
│   被 ≥2 个已安装 App 用到的 R 包,按引用计数管理            │
├────────────────────────────────────────────────────────────┤
│ Tier 2 · App 私有依赖  <userData>/private/<app-id>         │
│   只被 1 个 App 用到的包,或版本与共享池冲突的包            │
└────────────────────────────────────────────────────────────┘
  • 只有 Tier 0 是人工写死的;Tier 1 / Tier 2 由每个 bundle 的依赖清单 + 引用计数自动计算。
  • 运行时 R 版本固定为 4.6.1:壳的运行时和所有 App bundle 都必须用同一个 R 构建(二进制 ABI 必须一致)。

0.2 两套更新通道(不要混淆)

通道 对象 机制
壳更新 Electron 壳本身 electron-updater + Release 里的 latest.yml / latest-mac.yml,退出时静默覆盖安装
App 更新 单个 App 客户端拉 catalog.json,按 semver 比对,卡片出现 Update

0.3 相关仓库

仓库 角色
fentouxungui/ShinyApps 壳的配置 + catalog + schemas + 图标 + 构建脚本 + workflow
fentouxungui/shinyelectron(分支 multi-app-store) Electron 主进程 / 启动器 / 商店引擎 / R 后端
fentouxungui/SeuratExplorer App:源码 + standalone 安装器 + app-layer bundle CI
fentouxungui/scConvertShiny App:源码 + standalone 安装器 + app-layer bundle CI
mianaz/scConvert 第三方 R 包(scConvertShiny 的依赖,用 R CMD build 现建)

说明:「standalone 安装器」= 某个 App 自己独立打包成一个完整桌面程序(把壳 + 运行时 + 该 App 全烤进去),与「接入 ShinyApps 商店」是两条并行、互不干扰的产物。


1. 快速上手(用户)

  1. 从 ShinyApps Releases 下载 shinyapps-Setup-<ver>-x64.exe(Windows)或 .dmg(macOS)并安装。
  2. 打开 ShinyApps → 显示启动页(卡片网格)。
  3. 每张卡片有状态按钮:Install → 进度 → Run / Uninstall;有新版时出现 Update。
  4. 运行 App 后,回启动页用 Apps → Back to Launcher(或 Ctrl+L);会先弹「未保存分析将丢失」确认。
  5. 同一时刻只运行一个 App;切回启动页会杀掉当前 App 的整个 R 进程树。

1.1 数据与日志位置

Windows

%APPDATA%\shinyapps\
  ├─ state.json      # 已安装 App 与共享库引用计数
  ├─ apps\<id>\<ver>\# 解压后的 App bundle(app/ + rlib/)
  ├─ lib\            # 共享 R 库(Tier 1)
  ├─ private\<id>\   # App 私有库(Tier 2)
  ├─ store-cache\    # 下载缓存(.zip)
  └─ logs\           # app-*.log(含 [store] 前缀的商店日志)

安装目录:%LOCALAPPDATA%\Programs\shinyapps\

图标缓存问题:商店图标来自 catalog.json 的远程 URL;若加载失败,卡片会回退显示首字母。


2. 开发者指南:如何把你的 App 接入 ShinyApps

2.1 两种接入方式

方式 说明 适合
A. 商店 / 按需安装(推荐) 你的仓库自己产出预编译 bundle 发到自己的 Release;在 ShinyApps 的 catalog/apps.yml 登记即可。用户按需下载安装。 长期维护、希望加 App 不动壳
B. 打进壳(bake-in) 把 app.R + 依赖 tarball 放进 ShinyApps 仓库,重新构建壳。安装包即含该 App,无需下载。 离线、首装即用、App 很少且稳定

下面先讲推荐的 方式 A。

2.2 前提:你的 App 必须满足的契约

  1. 入口必须返回一个 shinyApp 对象,不能在内部调用 runApp()。因为 Electron 壳自己负责启动服务;App 里再 runApp() 会冲突。推荐像 sc_app() 那样导出一个「只 return(shinyApp(ui, server))」的函数:

    # scConvertShiny 导出的入口
    library(scConvertShiny)
    options(shiny.launch.browser = FALSE, shiny.deprecation.messages = FALSE)
    sc_app()
  2. 能产出一个标准 R 源码包(R CMD build)。bundle CI 会用它 + DESCRIPTION 里的 Imports/Depends/LinkingTo 计算依赖闭包。

  3. 版本号要能对上:Release tag(如 v0.1.10)去掉 v 必须等于 DESCRIPTION 里的 Version。否则源码 tarball 名对不上,会产出坏包(CI 已有「找不到源码包就 stop」的守卫)。

  4. R 版本固定 4.6.1(与壳运行时一致)。

2.3 方式 A · 逐步操作

自动化捷径: 下列步骤可由可复用工具包 fentouxungui/shinyapps-onboarding 全程自动完成——fork、bundle 工作流、tag/release、catalog 登记 + 图标、以及上游 PR。内含双模式 workflow 模板(扁平 app 与 R 包 app)、catalog 条目片段、驱动脚本与两个真实示例。

第 1 步:准备入口代码

在 App 仓库里确定一段入口(下面以 SeuratExplorer 为例,CI 会把它写成 bundle 里的 app/app.R):

library(SeuratExplorer)
options(shiny.launch.browser = FALSE, shiny.deprecation.messages = FALSE)
launchSeuratExplorer(verbose = FALSE,
  ReductionKeyWords = c("umap","tsne","pca"),
  SplitOptionMaxLevel = 12, MaxInputFileSize = 30 * 1024^3)

如果你的 App 是 R 包且入口函数不会自己 runApp(),直接用 library(<pkg>); <entry>() 即可。

第 2 步:复制 bundle 打包 workflow 到你的仓库

把下面这个 workflow 放到 你的 App 仓库 的 .github/workflows/(当前命名建议 build-app-bundle.yml;现网 SeuratExplorer/scConvertShiny 用的是带前缀的名字)。它会:

  • 在 windows-latest / macos-14 两个 runner 上,用 R 4.6.1 构建;
  • R CMD build 出源码包;
  • 计算完整传递依赖闭包(seed <- unique(c(loc_names, declared))),CRAN/Bioc 依赖用二进制装、本地/源码包用源码装,全部装进 bundle/rlib/;
  • 写出 bundle/manifest.json,并把 app/ + rlib/ + manifest.json 打成 zip;
  • 断言 zip 里必须含 rlib/(否则报错退出);
  • 计算 sha256,上传为 Release 资产 <id>-<version>-<platform>.zip。

你需要在 workflow 顶部改这些环境变量:

env:
  APP_ID: my-app              # 小写、[a-z0-9-],必须与 catalog/apps.yml 的 id 一致
  APP_NAME: "My App"
  APP_AUTHOR: "Your Name"
  APP_DESC: "..."
  APP_HOMEPAGE: "https://github.com/you/my-app"
  APP_LICENSE: "MIT"
  R_VERSION: "4.6.1"          # 不要改

并把你自己的入口代码写进「Assemble app + manifest.json」那一步(替换 writeLines(c(...)) 里的内容)。

产物命名契约(必须遵守):

<APP_ID>-<VERSION>-<PLATFORM>.zip
例:seurat-explorer-0.1.10-win-x64.zip
   scconvert-0.1.10-mac-arm64.zip
PLATFORM ∈ {win-x64, win-arm64, mac-x64, mac-arm64, linux-x64}

publish-catalog.yml 正是靠这个命名(<id>-<version>-<platform>.zip)自动解析 url / size / sha256。名字不对 → catalog 里不会出现该产物。

第 3 步:发布 Release,产出 bundle

两种触发方式(二选一):

  • 在 GitHub 上发布 Release(触发 release: published);或
  • Actions 里手动跑 Build app bundle,填写 tag(workflow_dispatch.inputs.tag)。

结果:Release 资产里出现两个 zip(win-x64 / mac-arm64),每个几十到几百 MB(含 rlib/)。

只推 tag 不会自动触发 bundle workflow(默认只跑 standalone 的 build-desktop.yml,而 GITHUB_TOKEN 创建的 Release 不会级联触发)。要特定行为可改成 release: published 触发(现网即如此)或 workflow_run。

第 4 步:在 ShinyApps 登记 App

编辑 ShinyApps/catalog/apps.yml,追加一项:

apps:
  - id: my-app
    repo: your-name/my-app                 # 用于 gh api 拉 latest release
    name: My App
    author: "Your Name"
    description: "..."
    homepage: "https://github.com/your-name/my-app"   # About > Visit Website;同时是卡片名字链接目标
    docs: "https://your-name.github.io/my-app/"        # 可选;Help > Documentation
    email: "you@example.org"                           # 可选;About > Email
    copyright: "(c) 2026 Your Name"                    # 可选;About 文本
    license: "MIT"
    icon: "https://fentouxungui.github.io/ShinyApps/icons/my-app.png"
    r_version: "4.6.1"
  • id 必须等于 bundle 的 APP_ID;
  • repo 必须让 gh api repos/<repo>/releases/latest 能拿到最新 Release;
  • icon 是绝对 URL(商店模式图标来自 catalog,而不是 _shinyelectron.yml)。
  • homepage / docs / email / author / copyright 用于填充运行中 App 的 Help / About;未填则回退到外壳自身的值,或该项不显示。

第 5 步:刷新 catalog

push catalog/apps.yml 到 main 即触发 Publish catalog;也可到 Actions 手动跑。它会把 catalog.json 发到 gh-pages:

https://fentouxungui.github.io/ShinyApps/catalog.json

第 6 步:客户端验证

重启 ShinyApps 壳 → 启动页出现新卡片 → Install → 进度 → Run。

建议:在 App 自己的 bundle workflow 末尾加一步 repository_dispatch(types: [bundle-updated])通知 ShinyApps 刷新 catalog(publish-catalog.yml 已监听该事件)。否则每次发新 bundle 后需等 10 分钟定时任务或手动跑。

2.4 图标

  • 卡片图标(每个 App):把 <id>.png(建议 512×512、透明背景)放进 ShinyApps/icons/,在 catalog/apps.yml 里写 https://fentouxungui.github.io/ShinyApps/icons/<id>.png。publish-catalog.yml 会把 icons/ 一并同步到 gh-pages(图标 URL 才可访问)。
  • 壳的程序图标(.exe/.dmg 图标):icons/shinyapps.png(≥1024)、icons/ico/shinyapps.ico、icons/mac/shinyapps.icns;由 build-suite.R 按平台选择并通过 export(icon=) 传入。
  • 注意:_shinyelectron.yml 里的 apps[].icon 在商店模式下不生效(图标来自 catalog),不要指望它。
  • 图标加载失败会回退成首字母,不会报错。

2.5 方式 B · 打进壳(bake-in)

当你想「下载即用、不经过商店」,把 App 直接烤进 ShinyApps 安装包:

  1. 入口:新建 ShinyApps/apps/<id>/app.R(同上文的入口代码)。
  2. 依赖源码包:把 App 及其本地依赖的 *.tar.gz 放进 ShinyApps/dependency/。
  3. 配置 ShinyApps/_shinyelectron.yml:
    • dependencies.r.local_packages 列出上一步的 tarball(仓库相对路径,如 dependency/SeuratExplorer_0.1.9.tar.gz);
    • dependencies.extra_packages / dependencies.r.packages 列出完整依赖闭包(renv 只扫 app.R 源码,扫不到 App 包内部的 Imports,必须显式列出);
    • apps: 数组增加一项 { id, name, description, path: ./apps/<id> }。
  4. 重构建壳:推一个 v* tag(或手动跑 Build ShinyApps desktop 并填 tag)。

优缺点:离线可用、无安装步骤;但安装包更大、每次加/改 App 都要重发壳、且更新 App 等于更新整个壳。


3. 维护者指南:仓库结构与工作流

3.1 ShinyApps 仓库结构(当前)

ShinyApps/
├─ _shinyelectron.yml              # 壳的配置(app 元数据、运行时策略、依赖、apps 数组、updates)
├─ build-suite.R                   # 构建脚本:由 tag 自动设置版本 → shinyelectron::export()
├─ shared-library-policy.md        # 共享库与兼容基线的契约(Tier 规则、引用计数)
├─ apps/
│  ├─ seurat-explorer/app.R        # bake-in 入口(方式 B)
│  └─ scconvert/app.R
├─ dependency/                     # bake-in 用的 R 源码包 tarball
│  ├─ SeuratExplorer_0.1.9.tar.gz  ├─ presto-1.1.0.tar.gz
│  ├─ scConvertShiny_0.1.1.tar.gz  └─ scConvert-0.2.1.tar.gz
├─ catalog/
│  └─ apps.yml                     # 唯一手工维护的 App 登记表
├─ schemas/
│  ├─ catalog.schema.json          # 服务端 catalog 的 schema
│  ├─ manifest.schema.json         # bundle 内 manifest.json 的 schema
│  ├─ state.schema.json            # 本地 state.json 的 schema
│  └─ catalog.json                 # 示例
├─ icons/
│  ├─ seurat-explorer.png  scconvert.png
│  ├─ shinyapps.png  shinyapps-512.png
│  ├─ ico/shinyapps.ico            # Windows 程序图标
│  └─ mac/shinyapps.icns           # macOS 程序图标
└─ .github/workflows/
   ├─ build-desktop.yml            # 构建壳安装器 + 发 Release
   └─ publish-catalog.yml          # 生成 catalog.json + 同步 icons 到 gh-pages

gh-pages 分支(发布产物,由 workflow 自动维护):

gh-pages/
├─ catalog.json
└─ icons/<id>.png

3.2 工作流总表

Workflow 所在仓库 触发 产物
Build ShinyApps desktop (build-desktop.yml) ShinyApps push tag v*;手动(输入 tag) shinyapps-Setup-<ver>-x64.exe/.dmg + latest*.yml + .blockmap,作为 Release 资产
Publish catalog (publish-catalog.yml) ShinyApps cron 每 10 分钟;手动;repository_dispatch: [bundle-updated];push main(改 catalog/apps.yml 或 icons/**) gh-pages 上的 catalog.json + icons/
Build app bundle (*-build-app-bundle.yml) 各 App 仓库 release: published;手动(输入 tag) <id>-<ver>-<platform>.zip(app/+rlib/+manifest.json),作为该 App Release 资产
standalone 安装器 (build-desktop.yml) 各 App 仓库 push tag v* 该 App 的独立完整安装器(与 ShinyApps 商店无关)

3.3 逐工作流详解

3.3.1 build-desktop.yml(ShinyApps)

  • 触发:push: tags: ['v*'];手动 workflow_dispatch(输入 tag)。
  • jobs:
    • build(matrix:windows-latest / macos-latest):
      1. checkout → r-lib/actions/setup-r(use-public-rspm)→ setup-node(node 24);
      2. 用 pak 装 fentouxungui/shinyelectron@multi-app-store + rlang/withr/renv;
      3. macOS 额外 brew install hdf5 pkg-config 并导出 PKG_CONFIG_PATH/HDF5_*(给 scConvert 的 configure 用);
      4. APP_VERSION=${{ inputs.tag || github.ref_name }},执行 source("build-suite.R");
      5. 上传 build/electron-app/dist/*.exe|.dmg|.blockmap|latest*.yml。
    • release:条件 release || tag v* || inputs.tag;下载 artifacts → softprops/action-gh-release 发布(generate_release_notes: true)。
  • 版本号:不用手改 _shinyelectron.yml——build-suite.R 会从 APP_VERSION 取得版本、去掉 v、只改 app.version 后 export()。本地不带 APP_VERSION 则保持原样。
  • 程序图标:build-suite.R 按平台在 icons/ico(Windows)/icons/mac(macOS)下挑图标传给 export(icon=)。

3.3.2 publish-catalog.yml(ShinyApps)

  • 触发:schedule: cron '*/10 * * * *';workflow_dispatch;repository_dispatch: types: [bundle-updated];push main 且路径为 catalog/apps.yml 或 icons/**。
  • 步骤:
    1. 读 catalog/apps.yml;对每个 App 用 gh api repos/<repo>/releases/latest 取最新 Release;
    2. 扫描资产名 <id>-<version>-<platform>.zip(版本必须等于 Release tag 去 v),提取 browser_download_url / size / digest(sha256);
    3. 生成 catalog/catalog.json(含 schema/updated_at/shell_min_version/apps[]);
    4. 用 worktree 把 catalog.json 与 icons/ 推到 gh-pages;
    5. 内容没有变化就不提交(比较时忽略 updated_at),避免每 10 分钟刷空提交。
  • 注意:定时任务只在默认分支生效,且 GitHub 常延迟(几分钟到更久)。

3.3.3 *-build-app-bundle.yml(各 App 仓库)

  • 触发:release: published;workflow_dispatch(输入 tag)。
  • matrix:windows-latest → win-x64、macos-14 → mac-arm64。
  • 关键逻辑:
    • setup-r 用 r-version: 4.6.1;
    • R CMD build --no-build-vignettes --no-manual;scConvertShiny 还会 git clone mianaz/scConvert 后 R CMD build;
    • 用 DESCRIPTION 的 Depends/Imports/LinkingTo 求声明依赖 declared,seed <- unique(c(loc_names, declared)),再 tools::package_dependencies(..., recursive=TRUE) 求完整传递闭包;
    • CRAN/Bioc 依赖 type="binary" 装、本地源码包 type="source" 装(本地 tarball 不能用 binary);
    • 生成 manifest.json(见 3.4),zip::zipr 打包并断言含 rlib/,算 sha256 写 meta-<platform>.json;
    • release job 把 zip 追加到对应 Release。

3.4 三份数据契约

catalog.json(gh-pages,客户端拉取) —— schemas/catalog.schema.json

{
  "schema": 1,
  "updated_at": "2026-10-08T07:10:25Z",
  "shell_min_version": "0.1.0",
  "apps": [
    {
      "id": "seurat-explorer",
      "name": "SeuratExplorer",
      "version": "0.1.10",
      "author": "...", "description": "...", "homepage": "...", "license": "...",
      "icon": "https://fentouxungui.github.io/ShinyApps/icons/seurat-explorer.png",
      "r_version": "4.6.1",
      "artifacts": {
        "win-x64":   { "url": "https://.../seurat-explorer-0.1.10-win-x64.zip",   "size": 215698175, "sha256": "ac4d81..." },
        "mac-arm64": { "url": "https://.../seurat-explorer-0.1.10-mac-arm64.zip", "size": 199743796, "sha256": "871245..." }
      }
    }
  ]
}

manifest.json(bundle 内,随 App 一起下载) —— schemas/manifest.schema.json

{
  "schema": 1,
  "id": "seurat-explorer", "name": "SeuratExplorer", "version": "0.1.10",
  "author": "...", "description": "...", "homepage": "...", "license": "...",
  "runtime": "r-shiny",
  "entry": "app/app.R",
  "platform": "win-x64",
  "r_version": "4.6.1",
  "min_shell_version": "0.1.0",
  "notes": "可选:平台已知限制",
  "packages": [ { "name": "Seurat", "version": "5.x", "path": "rlib/Seurat", "source": "cran" } ]
}

state.json(本地 %APPDATA%\shinyapps\state.json) —— schemas/state.schema.json

{
  "schema": 1,
  "apps":   { "seurat-explorer": { "version": "0.1.10", "platform": "win-x64", "dir": "...", "packages": ["Seurat", "..."] } },
  "shared": { "Seurat": { "version": "5.x", "refs": ["seurat-explorer", "scconvert"] } }
}

3.5 共享库策略(shared-library-policy.md)

  • 兼容基线:R 4.6.1;必须共享同版本的包:Seurat、SeuratObject、ggplot2、DT、hdf5r。
  • 放置规则(自动计算,不手写):
    • 一个包名被 ≥2 个已安装 App 以同一版本需要 → 放共享库 <userData>/lib;
    • 只被 1 个 App 需要,或版本与共享池不同 → 放私有库 <userData>/private/<app-id>;
    • 运行时 R_LIBS = <私有库> : <共享库>(私有优先)。
  • 引用计数:安装时同版本 refs += app、缺则复制并 refs=[app]、版本冲突进私有库;卸载时移除 ref,refs 归零才删共享包;更新时先解新版本、补新引用、释放被弃引用,最后才删旧目录(可回滚)。

4. 客户端运行机制

4.1 启动

启动页是商店模式:渲染卡片网格(name / description / 图标;图标失败回退首字母),拉 catalog.json(进程内缓存,重启最稳),把 catalog.version 与 state.json 的已装版本做 semver 比较,决定显示 Install / Run / Update / Stop / Uninstall。

4.2 安装(appstore.js)

  1. 拉 catalog → 按平台选 artifact;
  2. 下载:支持断点续传(HTTP Range)、失败重试 5 次、空闲 60s 超时、按 content-length 显示真实进度(downloading N / M MB);缓存目录 store-cache/;
  3. 校验:下载字节数 == catalog 的 size,且 sha256 匹配;
  4. 解压:内置零依赖 ZIP 解包(仅用 Node zlib,支持 stored/deflate + ZIP64 + zip-slip 防护),解到 apps/<id>/<version>/;
  5. 装依赖:把 rlib/ 里共享库缺失的包复制进 <userData>/lib,同版本 refs+1;版本冲突进私有库;
  6. 写 state.json。
  7. 反馈:卡片显示当前步骤(downloading % → verifying size → verifying checksum → unpacking → linking packages);失败时显示哪一步出错及原因——例如 Install failed at step "verify": size mismatch (catalog N bytes, downloaded M bytes)——且该提示常驻到下次安装。

4.3 运行 / 返回 / 进程回收

  • 运行时 R_LIBS = 私有库:共享库,用随包的便携 R 起 app/app.R(复用 native-r.js)。
  • 返回启动页(菜单 / Ctrl+L / app 内返回)都会:先弹未保存确认 → 确认后 stop() 杀掉整棵 R 进程树(Windows taskkill /PID <pid> /T /F)→ 等 status.phase === 'app_exit' 或超时兜底 → 加载启动页。
  • 同一时刻只有一个 App 后端。启动页时 Apps 菜单被隐藏(避免无意义的「Back to Launcher」)。
  • 卡片名字 → 主页:鼠标悬停 App 名字显示 “click to learn more”,点击用默认浏览器打开该 App 的 catalog homepage;没有 homepage 的 App 名字不可点击。
  • 按 App 的 Help/About:运行某 App 时,App 菜单标签、Help ▸ Documentation 与 About 弹窗使用该 App 的 name / version / description / author / copyright / homepage / email / docs;启动页时回退为外壳(ShinyApps)。

4.4 更新

  • 壳更新:启动时检查;在启动页通过 About → Check for Updates 检查;electron-updater 拉 Release 的 latest*.yml 比对,下载(.blockmap 差量),退出时 quitAndInstall() 静默覆盖。注意:未签名会下载成功但安装失败。
  • App 更新:catalog 版本 > 本地版本 → 卡片出现 Update;点击后停后端 → 下载 → 校验 → 解到新版本目录 → 原子切换 → 重算引用计数 → 删旧目录。完成后 main.js 的 done 事件带 op,launcher.html 乐观清除 Update(避免旧的回显丢失导致按钮不消失)。
    • 在 App 内运行时,Help → About → Check for Updates 检查的是该 App(与卡片 Update 同源):若有新版并确认,会停掉 App、回到启动页并执行更新(进度/失败显示在卡片上)。

4.5 卸载

停后端 → 删 App 目录与私有库 → 共享包 refs-1,归零才删。


5. 发布流程(维护者 checklist)

5.1 发一个新 App 版本(商店)

  1. App 仓库:改 DESCRIPTION 的 Version 并提交;
  2. 打 tag(如 v0.1.11)并发布 Release(或手动跑 Build app bundle 填 tag);
  3. 确认两个平台的 <id>-<ver>-<platform>.zip 已上传且大小正常(不是几 KB;应含 rlib/);
  4. 触发 Publish catalog(push、手动、或 App workflow 发 repository_dispatch),确认 catalog.json 里该 App 版本/sha256 已更新;
  5. 客户端重启 ShinyApps → 卡片出现 Update → 验证更新。

5.2 发一个新的壳版本

  1. 改壳相关文件(_shinyelectron.yml、build-suite.R、图标等);不需要手改版本号;
  2. 推 vX.Y.Z tag(或手动跑 Build ShinyApps desktop 填 tag);
  3. 确认 Release 里 .exe/.dmg + latest*.yml + .blockmap 齐全;
  4. (若要真正启用壳自动更新)配置代码签名证书。

5.3 只更新图标

把 PNG 放进 icons/ → push(触发 publish-catalog.yml)→ 确认 https://fentouxungui.github.io/ShinyApps/icons/<id>.png 200 → 重启壳。


6. 故障排查 FAQ

现象 根因 解决
装完还是显示 Update 界面刷新靠一次异步回显,长更新时可能丢失 已在 launcher.html 用 done(op) 乐观清除;升级壳
安装进度一直 0%,无报错 下载被截断(大小写不敏感的 Cache/ 撞名、无进度/超时/完整性校验) 已修:断点续传 + 重试 5 次 + size 校验 + 60s 空闲超时 + store-cache/
卡片无图标 gh-pages 上没有 icons/,或 CSP 拦截 用最新 publish-catalog.yml 同步 icons/;launcher.html 放行 img-src https:;失败会回退首字母
图标带蓝色背景 / 太小 .app-icon 有 background 且尺寸小 已修:有图时透明背景、容器 64px
壳打不开、启动即退 appstore.js 没被打进包(require('./backends/appstore') 失败) 确认 appstore.js 放在 inst/electron/backends/,且 R/build-templates.R 的复制列表包含它
bundle zip 只有 ~2.5 KB 只打了 app/+manifest.json,rlib/ 丢失 用 zip::zipr 打包并断言含 rlib/;CI 已加守卫
bundle CI 报 not available as a binary 本地源码 tarball 不能用 type="binary" 本地包用 type="source",CRAN/Bioc 闭包用 binary
依赖缺 RANN 等传递依赖 闭包只从 loc_names 展开,漏了 declared seed <- unique(c(loc_names, declared))
macOS build 挂在 scConvert configure 找不到 HDF5 brew install hdf5 pkg-config 并导出 PKG_CONFIG_PATH/HDF5_*
tag 与 DESCRIPTION 版本不一致 → 坏包 workflow 找不到 Pkg_<ver>.tar.gz 对齐版本;CI 已加「源码包缺失即 stop」
壳自动更新下完装不上 未签名 购买/配置代码签名(signing.sign 目前为 false)
Windows scConvert 转换报 H5Fclose CRAN hdf5r 自带 HDF5 1.12.1 的已知限制 见第 7 节,暂挂
点 Apps → Back to Launcher 报 ReferenceError leaveToLauncher 作用域不对 确保它在模块顶层(上游 PR 已整理)

7. 已知限制与建议补充

7.1 当前已知限制

  1. 平台:便携 R 的 bundled 策略目前只有 Windows / macOS(Linux 需 system);bundle CI 目前只产 win-x64 与 mac-arm64。
  2. Windows scConvert 转换:CRAN hdf5r 二进制带 HDF5 1.12.1,H5Fclose 终结器在 GC 时可能失败(不可捕获)。已在 manifest.notes 标注,待单独处理。
  3. 代码签名:Windows/macOS 未签名 → SmartScreen/Gatekeeper 警告;壳自动更新下载成功但安装失败。
  4. 空间冗余:安装后 apps/<id>/<ver>/rlib/ 与共享库 lib/ 重复;store-cache/ 的 zip 未清理。建议装完删 rlib/、清理缓存。
  5. 切 App 丢状态:一次只跑一个 App,返回启动页会杀掉进程,未保存分析丢失(已有确认框)。
  6. catalog 刷新依赖定时/手动:App 发布后不会自动通知 ShinyApps(除非加 repository_dispatch)。
  7. 许可:shinyelectron 为 AGPL-3.0;SeuratExplorer 为 GPL-3.0-or-later;scConvertShiny 为 MIT。再分发时需遵守各自义务。

7.2 我认为需要补充的内容(按优先级)

高优先级

  1. README.md(仓库首页):目前仓库缺一个面向新人的入口。应含一句话简介、架构图、快速开始、指向 Wiki 各页。
  2. LICENSE 与 THIRD-PARTY-NOTICES:明确 ShinyApps 自身许可,并列出 shinyelectron(AGPL-3.0)、各 App、第三方 R 包(Seurat、scConvert 等)的许可与义务。
  3. 「新增 App」模板 + 校验:提供一个可直接复制的 build-app-bundle.yml 模板与 apps.yml 片段;再写一个 CI/脚本做契约校验(id 合法、命名 id-ver-platform.zip、tag==DESCRIPTION 版本、r_version==4.6.1、zip 含 rlib/)。
  4. 自动刷新 catalog:在各 App 的 bundle workflow 末尾加 repository_dispatch: [bundle-updated],把「手动跑 Publish catalog」变成自动。
  5. 版本兼容性矩阵:集中记录「壳版本 × 运行时 R 版本 × 必需共享包基线」,让 App 开发者一眼知道该按什么构建。
  6. 代码签名指南:Windows(CSC_LINK/CSC_KEY_PASSWORD)与 macOS(notarization)的配置步骤;说明签名前的自动更新行为。

中优先级

  1. 发布 checklist / 回滚手册:把第 5 节做成勾选清单;补「如何回滚一个坏 App 版本 / 坏壳版本」。
  2. 故障排查扩容:FAQ 增加「如何读 %APPDATA%\shinyapps\logs\app-*.log 的 [store] 行」。
  3. 测试清单:菜单返回、未保存提示两分支、反复切换、真实转换、进程回收、退出无残留、大文件、快速连点、冷启动耗时/内存——可作为发布前验收。
  4. 空间优化:实现「装完删 bundle rlib/」与「清理 store-cache」,并在文档里说明收益。
  5. 安全说明:catalog 与 bundle 视为不可信输入;只走 HTTPS;校验 sha256(建议后续加签名/验签)。
  6. 截图与动图:启动页、安装进度、更新提示、返回确认框——Wiki 可读性会好很多。
  7. 中英文对照:至少 Home 与快速开始提供英文版。

低优先级 / 长期

  1. CHANGELOG.md:壳与各 App 的版本变更;catalog 已预留 changelog 字段可用于更新弹窗展示。
  2. App 详情页:卡片展开显示作者/许可/版本/更新日志(catalog 已有这些字段)。
  3. 多语言 / 主题 / 搜索:启动页体验增强。
  4. 贡献与治理:CONTRIBUTING.md、CODE_OF_CONDUCT.md、Issue/PR 模板。
  5. CI 端到端测试:在 CI 里对生成的 bundle 做「解压 → 校验 manifest → 冒烟启动」的最小验证。

8. 附:向上游贡献(可选)

multi-app-store 分支混合了「通用壳能力」与「本项目专属逻辑」,不建议整体开 PR。现已把其中最通用的一块——返回启动页前确认 + 启动页隐藏 Apps 菜单——整理成只含 inst/electron/shared/main.js 的干净补丁,对上游 main 生成:

git remote add upstream https://github.com/coatless-rpkg/shinyelectron   # 若已有则跳过
git fetch upstream
git checkout -b confirm-back-to-launcher upstream/main
git apply feature1-unsaved-prompt.patch
git add inst/electron/shared/main.js
git commit -m "Confirm before returning to the launcher; hide Apps menu on launcher"
git push origin confirm-back-to-launcher

然后对 coatless-rpkg:main 开 PR。应用商店 / 安装器相关代码建议单独走 Issue 或后续 PR。


附录 A · 关键文件片段

A.1 _shinyelectron.yml(壳配置要点)

app: { name: "ShinyApps", version: "0.1.0", slug: "shinyapps" }
build: { type: "r-shiny", runtime_strategy: "bundled" }
installer: { app_id: "com.shinyapps.desktop" }
signing: { sign: false }

dependencies:
  extra_packages: [SeuratExplorer, presto, Seurat, SeuratObject, ggplot2, DT,
                   scConvertShiny, scConvert, hdf5r, crayon, shiny, shinydashboard, callr, zip]
  r:
    version: "4.6.1"
    local_packages:
      - "dependency/SeuratExplorer_0.1.9.tar.gz"
      - "dependency/presto-1.1.0.tar.gz"
      - "dependency/scConvertShiny_0.1.1.tar.gz"
      - "dependency/scConvert-0.2.1.tar.gz"

updates:
  enabled: true
  provider: "github"
  check_on_startup: true
  auto_download: true
  auto_install: true
  github: { owner: "fentouxungui", repo: "ShinyApps" }

apps:
  - { id: seurat-explorer, name: "SeuratExplorer", path: "./apps/seurat-explorer" }
  - { id: scconvert,       name: "scConvertShiny", path: "./apps/scconvert" }

版本号由 build-suite.R 在构建时按 tag 自动改写,不要在发版时手改。

A.2 catalog/apps.yml 字段

schema: 1
shell_min_version: "0.1.0"
apps:
  - id: <小写唯一 id>
    repo: <owner/repo>            # gh api 拉 latest release
    name: <显示名>
    author: <作者>                # About 文本
    description: <描述>           # About 文本
    homepage: <URL>               # About > Visit Website;同时是卡片名字链接目标
    docs: <URL>                   # 可选;Help > Documentation
    email: <地址>                 # 可选;About > Email
    copyright: <文本>             # 可选;About 文本
    license: <SPDX>
    icon: https://fentouxungui.github.io/ShinyApps/icons/<id>.png
    r_version: "4.6.1"

A.3 App 入口 app.R

library(<YourPkg>)
options(shiny.launch.browser = FALSE, shiny.deprecation.messages = FALSE)
<entry_function>()      # 必须 return 一个 shinyApp 对象,不要 runApp()

附录 B · 术语表

术语 含义
壳 / shell Electron 桌面程序本体(含便携 R 运行时)
bundle 单个 App 的预编译包:app/ + rlib/ + manifest.json
catalog 托管在 gh-pages 的 catalog.json,App 列表与下载地址/校验和
runtime 层 Tier 0,随壳的一次性底座
共享库 Tier 1,<userData>/lib,引用计数
私有库 Tier 2,<userData>/private/<id>
R_LIBS R 包搜索路径,运行顺序 = 私有库:共享库

本文档由 Open-Science Agent 依据 2026-09/10 的开发与验收会话整理,并核对当前 ShinyApps / shinyelectron@multi-app-store / 各 App 仓库状态。若仓库后续变更,请以仓库内实际文件为准。

🌐 Language / 语言


ShinyApps Wiki

Clone this wiki locally