Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
181 changes: 181 additions & 0 deletions MiniApp/Demo/git-graph/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# Git Graph Demo MiniApp / Git Graph 示例 MiniApp

[English](#english) | [中文](#中文)

---

## English

This demo showcases BitFun MiniApp's full-stack collaboration capability — specifically, using a third-party npm package (`simple-git`) inside a Node.js/Bun Worker to read a local Git repository and render an interactive commit graph.

### Features

- **Pick a repository** — opens a native directory picker via `app.dialog.open({ directory: true })`
- **Commit graph** — Worker fetches `git.graphData` (log + refs + stashes + uncommitted in one call); UI renders commit nodes and branch lines as SVG
- **Branches & status** — shows current branch, full branch list, and workspace status (modified / staged / untracked)
- **Commit detail** — click any commit to view author, timestamp, diff stat, and per-file diff
- **Branch management** — create, delete, rename, checkout local/remote branches
- **Merge / Rebase** — merge a branch into HEAD, rebase HEAD onto a branch
- **Cherry-pick / Revert / Reset** — cherry-pick a commit, revert with a new commit, or hard/mixed/soft reset
- **Push / Fetch** — push to remote, fetch with prune, fetch a remote branch into a local branch
- **Remote management** — add, remove, update remote URLs via the Remotes panel
- **Stash** — push, apply, pop, drop, and branch-from-stash operations
- **Search commits** — filter commit list by message, hash, or author
- **Tag management** — add lightweight or annotated tags, delete tags, push tags

### Data Flow

1. **UI → Bridge**: `app.call('git.log', { cwd, maxCount })` etc. via `window.app` (JSON-RPC)
2. **Bridge → Tauri**: postMessage intercepted by the host `useMiniAppBridge`, which calls `miniapp_worker_call`
3. **Tauri → Worker**: Rust writes the request to Worker stdin (JSON-RPC)
4. **Worker**: `worker_host.js` loads `source/worker.js`; exported handlers are invoked — primarily `git.graphData` (returns commits + refs + stashes + uncommitted in one response), plus `git.show`, `git.checkout`, `git.merge`, `git.push`, `git.stashPush`, and 20+ other methods — all backed by the `simple-git` npm package
5. **Worker → Tauri → Bridge → UI**: response travels back via stderr → Rust → postMessage to iframe → UI refreshes graph and detail panel

### Directory Structure

```
miniapps/git-graph/
├── README.md # this file
├── meta.json # metadata & permissions (fs/shell/node)
├── package.json # npm deps (simple-git) + build script
├── storage.json # app KV (e.g. last opened repo path)
└── source/
├── index.html # UI skeleton
├── build.js # build script: concat ui/*.js → ui.js, styles/*.css → style.css
├── ui.js # frontend entry (generated by build, do not edit directly)
├── style.css # style entry (generated by build, do not edit directly)
├── ui/ # frontend modules (run `npm run build` after editing)
│ ├── state.js, theme.js, main.js, bootstrap.js
│ ├── graph/layout.js, graph/renderRowSvg.js
│ ├── components/contextMenu.js, modal.js, findWidget.js
│ ├── panels/detailPanel.js, remotePanel.js
│ └── services/gitClient.js
├── styles/ # style modules (run `npm run build` after editing)
│ ├── tokens.css, layout.css, graph.css
│ ├── detail-panel.css, overlay.css
│ └── (merge order defined in build.js)
├── worker.js # backend CJS (simple-git wrapper)
└── esm_dependencies.json # ESM deps (empty for this demo)
```

**During development**: after editing `source/ui/*.js` or `source/styles/*.css`, run `npm run build` inside the `miniapps/git-graph` directory to regenerate `source/ui.js` and `source/style.css`. BitFun will pick up the latest build on next load.

### Running in BitFun

1. **Install to user data directory**: copy this folder into BitFun's MiniApp data directory under an `app_id` subdirectory, e.g.:
- The data directory is typically `{user_data}/miniapps/`
- Create a subdirectory like `git-graph-sample` and place all files from this folder inside it (i.e. `meta.json`, `package.json`, `source/` etc. at the root of that subdirectory)

2. **Or import via API**: if BitFun supports path-based import, use `create_miniapp` or equivalent, pointing to this directory as the source; make sure the `id` in `meta.json` matches the directory name.

3. **Install dependencies**: inside the MiniApp's app directory, run:
- `bun install` or `npm install` (matching the runtime BitFun detected)
- Or use the "Install Dependencies" action in Toolbox (calls `miniapp_install_deps`)

4. **Compile**: to regenerate `compiled.html`, call `miniapp_recompile` or let BitFun compile automatically when the MiniApp is opened.

5. Open the MiniApp in the Toolbox scene, pick a repository, and the Git Graph will appear.

### Permissions

| Permission | Value | Purpose |
|---|---|---|
| `fs.read` | `{appdata}`, `{workspace}`, `{user-selected}` | Read app data, workspace, and user-selected repo |
| `fs.write` | `{appdata}` | Write app-own data only (e.g. storage) |
| `shell.allow` | `["git"]` | `simple-git` needs to invoke the system `git` binary |
| `node.enabled` | `true` | Enable JS Worker to execute `simple-git` logic in `worker.js` |

### Technical Highlights

- **Client-side third-party library**: `require('simple-git')` in `worker.js` runs inside a Bun or Node.js Worker process — no need to reimplement Git in Rust
- **Zero custom dialect**: UI is plain concatenated JavaScript (IIFE modules sharing `window.__GG`), Worker is standard CJS — no custom framework or transpiler required; `window.app` is the unified Bridge API
- **ESM dependencies**: this demo uses plain vanilla JS; `esm_dependencies.json` is empty — add React, D3, etc. there to have them served via Import Map from esm.sh

---

## 中文

本示例展示 BitFun MiniApp 的前后端协同能力,尤其是通过 Node.js/Bun 在端侧使用三方 npm 库(`simple-git`)读取 Git 仓库并渲染交互式提交图谱。

### 功能

- **选择仓库**:通过 `app.dialog.open({ directory: true })` 打开原生目录选择器,选择本地 Git 仓库
- **提交图谱**:Worker 端通过 `git.graphData`(一次调用返回提交列表 + refs + stash + 未提交变更),UI 端用 SVG 绘制提交节点与分支线
- **分支与状态**:展示当前分支、所有分支列表及工作区状态(modified/staged/untracked)
- **提交详情**:点击某个 commit 可查看作者、时间、diff stat 及逐文件 diff
- **分支管理**:创建、删除、重命名、checkout 本地/远程分支
- **Merge / Rebase**:将分支合并到 HEAD,或将 HEAD rebase 到目标分支
- **Cherry-pick / Revert / Reset**:cherry-pick 指定 commit,revert 生成新提交,或 hard/mixed/soft reset
- **Push / Fetch**:推送到远程,带 prune 的 fetch,将远程分支 fetch 到本地分支
- **远程管理**:通过远程面板添加、删除、修改远程 URL
- **Stash**:push、apply、pop、drop 及从 stash 创建分支
- **搜索提交**:按消息、hash 或作者过滤提交列表
- **Tag 管理**:添加轻量/注解 tag,删除 tag,推送 tag

### 前后端协同流程

1. **UI → Bridge**:`app.call('git.log', { cwd, maxCount })` 等通过 `window.app` 发起 RPC
2. **Bridge → Tauri**:postMessage 被宿主 `useMiniAppBridge` 接收,调用 `miniapp_worker_call`
3. **Tauri → Worker**:Rust 将请求写入 Worker 进程 stdin(JSON-RPC)
4. **Worker**:`worker_host.js` 加载本目录 `source/worker.js`,其导出的处理函数被调用 — 主要是 `git.graphData`(一次返回提交 + refs + stash + 未提交变更),以及 `git.show`、`git.checkout`、`git.merge`、`git.push`、`git.stashPush` 等 20+ 个方法 — 均基于 `simple-git` npm 包
5. **Worker → Tauri → Bridge → UI**:响应经 stderr 回传 Rust,再 postMessage 回 iframe,UI 更新图谱与详情

### 目录结构

```
miniapps/git-graph/
├── README.md # 本说明
├── meta.json # 元数据与权限(fs/shell/node)
├── package.json # npm 依赖(simple-git)及 build 脚本
├── storage.json # 应用 KV(如最近打开的仓库路径)
└── source/
├── index.html # UI 骨架
├── build.js # 构建脚本:合并 ui/*.js → ui.js,styles/*.css → style.css
├── ui.js # 前端入口(由 build 生成,勿直接编辑)
├── style.css # 样式入口(由 build 生成,勿直接编辑)
├── ui/ # 前端模块(编辑后需运行 npm run build)
│ ├── state.js, theme.js, main.js, bootstrap.js
│ ├── graph/layout.js, graph/renderRowSvg.js
│ ├── components/contextMenu.js, modal.js, findWidget.js
│ ├── panels/detailPanel.js, remotePanel.js
│ └── services/gitClient.js
├── styles/ # 样式模块(编辑后需运行 npm run build)
│ ├── tokens.css, layout.css, graph.css
│ ├── detail-panel.css, overlay.css
│ └── (合并顺序见 build.js)
├── worker.js # 后端 CJS(simple-git 封装)
└── esm_dependencies.json # ESM 依赖(本示例为空)
```

**开发时**:修改 `source/ui/*.js` 或 `source/styles/*.css` 后,在 `miniapps/git-graph` 目录执行 `npm run build` 生成 `source/ui.js` 与 `source/style.css`,BitFun 加载时会使用最新构建结果。

### 在 BitFun 中运行

1. **安装到用户数据目录**:将本目录复制到 BitFun 的 MiniApp 数据目录下,并赋予一个 app_id 子目录,例如:
- 数据目录一般为 `{user_data}/miniapps/`
- 新建子目录如 `git-graph-sample`,将本目录中所有文件按相同结构放入其中(即 `meta.json`、`package.json`、`source/` 等在该子目录下)

2. **或通过 API 创建**:若 BitFun 支持从路径导入,可使用 `create_miniapp` 或等价方式,将本目录作为 source 路径导入,并确保 `meta.json` 中的 `id` 与目录名一致。

3. **安装依赖**:在 MiniApp 的 app 目录下执行:
- `bun install` 或 `npm install`(与 BitFun 检测到的运行时一致)
- 或在 Toolbox 中对该 MiniApp 执行「安装依赖」操作(调用 `miniapp_install_deps`)

4. **编译**:若需重新生成 `compiled.html`,可调用 `miniapp_recompile` 或由 BitFun 在打开该 MiniApp 时自动编译。

5. 在 Toolbox 场景中打开该 MiniApp,选择仓库后即可查看 Git Graph。

### 权限说明

| 权限 | 值 | 用途 |
|---|---|---|
| `fs.read` | `{appdata}`、`{workspace}`、`{user-selected}` | 读取应用数据、工作区及用户选择的仓库目录 |
| `fs.write` | `{appdata}` | 仅写入应用自身数据(如 storage) |
| `shell.allow` | `["git"]` | `simple-git` 需调用系统 `git` 命令 |
| `node.enabled` | `true` | 启用 JS Worker,以便执行 `worker.js` 中的 `simple-git` 逻辑 |

### 技术要点

- **端侧三方库**:`worker.js` 中 `require('simple-git')`,在 Bun 或 Node.js Worker 进程中运行,无需在 Rust 中重新实现 Git 能力
- **无自定义方言**:UI 为普通拼接脚本(IIFE 模块通过 `window.__GG` 共享状态),Worker 为标准 CJS,无需自定义框架或转译器;`window.app` 为统一 Bridge API
- **ESM 依赖**:本示例 UI 使用纯 vanilla JS,`esm_dependencies.json` 为空;若需 React/D3 等,可在其中声明并由 Import Map 从 esm.sh 加载
25 changes: 25 additions & 0 deletions MiniApp/Demo/git-graph/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"id": "git-graph-sample",
"name": "Git Graph",
"description": "交互式 Git 提交图谱,通过 Worker 端 simple-git 读取仓库,UI 端 SVG 渲染。展示前后端协同与端侧三方库使用。",
"icon": "📊",
"category": "developer",
"tags": ["git", "graph", "example"],
"version": 1,
"created_at": 0,
"updated_at": 0,
"permissions": {
"fs": {
"read": ["{appdata}", "{workspace}", "{user-selected}"],
"write": ["{appdata}"]
},
"shell": { "allow": ["git"] },
"net": { "allow": [] },
"node": {
"enabled": true,
"max_memory_mb": 256,
"timeout_ms": 30000
}
},
"ai_context": null
}
10 changes: 10 additions & 0 deletions MiniApp/Demo/git-graph/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "miniapp-git-graph",
"private": true,
"scripts": {
"build": "node source/build.js"
},
"dependencies": {
"simple-git": "^3.27.0"
}
}
53 changes: 53 additions & 0 deletions MiniApp/Demo/git-graph/source/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Git Graph MiniApp — build: concatenate source/ui/*.js → ui.js, source/styles/*.css → style.css.
* Run from miniapps/git-graph: node source/build.js
*/
const fs = require('fs');
const path = require('path');

const SOURCE_DIR = path.join(__dirname);
const ROOT = path.dirname(SOURCE_DIR);

const UI_ORDER = [
'ui/state.js',
'ui/theme.js',
'ui/graph/layout.js',
'ui/graph/renderRowSvg.js',
'ui/services/gitClient.js',
'ui/components/contextMenu.js',
'ui/components/modal.js',
'ui/components/findWidget.js',
'ui/panels/remotePanel.js',
'ui/panels/detailPanel.js',
'ui/main.js',
'ui/bootstrap.js',
];

const STYLES_ORDER = [
'styles/tokens.css',
'styles/layout.css',
'styles/graph.css',
'styles/detail-panel.css',
'styles/overlay.css',
];

function concat(files, dir) {
let out = '';
for (const f of files) {
const full = path.join(dir, f);
if (!fs.existsSync(full)) {
console.warn('Missing:', full);
continue;
}
out += '/* ' + f + ' */\n' + fs.readFileSync(full, 'utf8') + '\n';
}
return out;
}

const uiOut = path.join(SOURCE_DIR, 'ui.js');
const styleOut = path.join(SOURCE_DIR, 'style.css');

fs.writeFileSync(uiOut, concat(UI_ORDER, SOURCE_DIR), 'utf8');
fs.writeFileSync(styleOut, concat(STYLES_ORDER, SOURCE_DIR), 'utf8');

console.log('Built', uiOut, 'and', styleOut);
1 change: 1 addition & 0 deletions MiniApp/Demo/git-graph/source/esm_dependencies.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Loading