@hunan36/dsh-git-flow 0.0.0-stage → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +253 -2
- package/README.zh.md +262 -0
- package/cordis.patch.yml +3 -0
- package/lib/client.js +2138 -0
- package/lib/index.js +1347 -0
- package/lib/types/client/BranchChip.d.ts +8 -0
- package/lib/types/client/BranchMenu.d.ts +27 -0
- package/lib/types/client/CommitDialog.d.ts +45 -0
- package/lib/types/client/GitFlowDialog.d.ts +37 -0
- package/lib/types/client/api.d.ts +42 -0
- package/lib/types/client/index.d.ts +14 -0
- package/lib/types/client/locales.d.ts +236 -0
- package/lib/types/client/styles.d.ts +12 -0
- package/lib/types/contract.d.ts +207 -0
- package/lib/types/git.d.ts +86 -0
- package/lib/types/index.d.ts +12 -0
- package/lib/types/message.d.ts +27 -0
- package/lib/types/routes.d.ts +14 -0
- package/lib/types/service.d.ts +178 -0
- package/package.json +92 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 hunan36
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,254 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-git-flow
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
A DeepSeek Harness workspace git plugin: a branch chip in the composer tool row. Open it to browse branches, switch branches, create one, and commit a checked subset of files — the commit message is written by the session's own model from the diff.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
composer tool row: [⑂ feature/login 3] ← click (a Feature badge sits beside it)
|
|
9
|
+
├ Search branches
|
|
10
|
+
├ Local branches · 2 ✓ feature/login
|
|
11
|
+
│ main
|
|
12
|
+
├ New branch…
|
|
13
|
+
├ Commit… (enabled while the tree is dirty)
|
|
14
|
+
├ Refresh status
|
|
15
|
+
├ Start git flow branch…
|
|
16
|
+
└ Finish git flow branch… (on a flow branch, with a clean tree)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
Two ways in; either one is enough.
|
|
22
|
+
|
|
23
|
+
### From the plugin page (GUI)
|
|
24
|
+
|
|
25
|
+
Sidebar → **Plugins** → **Add plugin**, then put this in "Package or address":
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
@hunan36/dsh-git-flow
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Press Install and enable it afterwards ("Enable"). That path runs `pnpm add` in **the profile serving the page** (usually `web`) and writes this package into `dsh.profile.bundles` for you — you do neither by hand.
|
|
32
|
+
|
|
33
|
+
The GitHub address `https://github.com/hunan36/dsh-git-flow` also works, but `pnpm` pins the dependency to that commit and will not follow new releases. Installing by package name is what makes version comparison and updates possible.
|
|
34
|
+
|
|
35
|
+
**Restart `dsh web`** afterwards: host-half changes are not hot-reloaded, and the browser fetches the new client bundle on restart.
|
|
36
|
+
|
|
37
|
+
## Updating
|
|
38
|
+
|
|
39
|
+
DSH does not auto-update third-party plugins; updating takes one command:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pnpm --dir ~/.dsh/profiles/web add @hunan36/dsh-git-flow@latest
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Then **restart `dsh web`**. Host-half changes (services and API routes) only apply after a restart; the browser half refetches its bundle then.
|
|
46
|
+
|
|
47
|
+
Check the installed and latest versions:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm view @hunan36/dsh-git-flow version
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Installs by GitHub address are not followed automatically (`pnpm` pinned a commit), so reinstall explicitly to update:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pnpm --dir ~/.dsh/profiles/web add github:hunan36/dsh-git-flow#main
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### From the CLI (a dedicated profile is recommended)
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# 1. Create a dedicated profile (leaves your existing web profile alone)
|
|
63
|
+
dsh --profile gitflow-web --from-default-profile web
|
|
64
|
+
|
|
65
|
+
# 2. Add the plugin — the npm package, a git URL, or a local checkout path
|
|
66
|
+
dsh plugin --profile gitflow-web add @hunan36/dsh-git-flow
|
|
67
|
+
# dsh plugin --profile gitflow-web add https://github.com/hunan36/dsh-git-flow
|
|
68
|
+
# dsh plugin --profile gitflow-web add "$PWD"
|
|
69
|
+
|
|
70
|
+
# 3. Enable it on the plugin page ("Enable"), or edit the bundle list directly:
|
|
71
|
+
# dsh.profile.bundles += "@hunan36/dsh-git-flow"
|
|
72
|
+
# The package declares dsh.bundle.patch, so enabling it mounts the GitFlow
|
|
73
|
+
# service from the bundle's own cordis.patch.yml.
|
|
74
|
+
|
|
75
|
+
# 4. Boot
|
|
76
|
+
dsh --profile gitflow-web
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The `headless` profile installs too, but it has no `webServer`: no `/api/dsh-git-flow/*` route is registered, the browser half does not exist there, and boot reports no error.
|
|
80
|
+
|
|
81
|
+
### A machine without pnpm
|
|
82
|
+
|
|
83
|
+
dsh's plugin management — the plugin page's Install, and `dsh plugin …` — shells out to pnpm, and fails outright when there is none:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
dsh: pnpm was not found; install pnpm and make it available on PATH.
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Install one (Node ships npm):
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npm install -g pnpm # the latest 10.x
|
|
93
|
+
# or: corepack enable && corepack prepare pnpm@10 --activate
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Do not let Corepack use its own default: after a bare `corepack enable` you get pnpm **8.15.7**, which walks straight into the `ERR_PNPM_ADDING_TO_ROOT` failure below.
|
|
97
|
+
|
|
98
|
+
If you would rather not install pnpm at all, the plugin itself does not need it (its `dependencies` are empty):
|
|
99
|
+
|
|
100
|
+
- **Use Node's npm**: `cd ~/.dsh/profiles/<profile> && npm install https://github.com/hunan36/dsh-git-flow`
|
|
101
|
+
- **Copy the directory**: drop the whole `dsh-git-flow` folder (including `lib/`) into `<profile>/node_modules/`, for offline or fully manual setups
|
|
102
|
+
|
|
103
|
+
Both need one extra step: add `dsh-git-flow` to that profile's `dsh.profile.bundles` in its `package.json`, then restart `dsh web`. The plugin page and `dsh plugin add` do that for you; a manual install does not, and the package stays unloaded even though it sits in `node_modules`.
|
|
104
|
+
|
|
105
|
+
### Point the plugin page at npm (a machine that only has npm)
|
|
106
|
+
|
|
107
|
+
The plugin page runs `pnpm add <spec>` by default, but the plugin manager's command is configurable — set it to `npm` in the profile's `cordis.patch.yml` and the page installs with npm:
|
|
108
|
+
|
|
109
|
+
```yaml
|
|
110
|
+
# ~/.dsh/profiles/<profile>/cordis.patch.yml
|
|
111
|
+
- id: plugin-manager
|
|
112
|
+
config:
|
|
113
|
+
pnpmCommand: npm
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Then use the plugin page exactly as usual (paste the address → Install → Enable). The CLI's `dsh plugin … add` does **not** honour this setting and still needs pnpm.
|
|
117
|
+
|
|
118
|
+
Three caveats:
|
|
119
|
+
|
|
120
|
+
- This is an internal dsh setting and may move between versions; prefer installing pnpm when the machine allows it.
|
|
121
|
+
- The arguments dsh passes are `add <spec>`, `remove <name>`, and `view <spec> … --json`, all of which npm accepts; but pnpm-specific semantics (`-w`, `onlyBuiltDependencies`, lockfiles) do not exist under npm, so **do not mix both managers in one profile**.
|
|
122
|
+
- The command line shown in the UI and logs still reads `pnpm add …` (dsh builds that label literally). To tell which manager really ran, look for `package-lock.json` versus `pnpm-lock.yaml` in the profile directory.
|
|
123
|
+
|
|
124
|
+
### Installation fails with ERR_PNPM_ADDING_TO_ROOT
|
|
125
|
+
|
|
126
|
+
A dsh profile directory is itself a pnpm workspace (`pnpm-workspace.yaml` with `packages: [.]`), and pnpm 8 treats `pnpm add` there as adding to a workspace root — which it refuses. When the profile has no `packageManager` field, Node's Corepack fills one in (commonly `pnpm@8.15.7`), and the install then dies on that check **before it ever resolves this plugin**, so the failure is not about this package.
|
|
127
|
+
|
|
128
|
+
Any one of these gets you through:
|
|
129
|
+
|
|
130
|
+
1. **Installing from the plugin page (GUI)**: that path runs `pnpm add <spec>` and cannot pass `-w`, so do step 2 first (write the `.npmrc`) and then press "Retry" in the dialog — the same command then succeeds under pnpm 8.
|
|
131
|
+
2. Teach the profile to accept a root dependency, then install exactly as documented:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
echo 'ignore-workspace-root-check=true' >> ~/.dsh/profiles/gitflow-web/.npmrc
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
3. From the CLI, pass `-w` through (dsh forwards extra arguments to pnpm):
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
dsh plugin --profile gitflow-web add -w https://github.com/hunan36/dsh-git-flow.git
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
4. Put the profile on pnpm 10 (the version this plugin is tested with): run `corepack use pnpm@10` in the profile, or set the `packageManager` field Corepack added to `pnpm@10.22.0`.
|
|
144
|
+
|
|
145
|
+
Note that after the first failure Corepack has already written `packageManager: pnpm@8.15.7+sha512…` into that profile's `package.json`, so every later plugin operation there uses pnpm 8 — step 4 is the durable fix.
|
|
146
|
+
|
|
147
|
+
Requires Node `^22.19.0 || >=24.0.0` and pnpm 10; this repository pins it with `packageManager: pnpm@10.22.0`.
|
|
148
|
+
|
|
149
|
+
## Configuration
|
|
150
|
+
|
|
151
|
+
Set in `cordis.patch.yml`:
|
|
152
|
+
|
|
153
|
+
| Key | Default | Meaning |
|
|
154
|
+
|---|---|---|
|
|
155
|
+
| `timeoutMs` | 15000 | Deadline for read-only and local git commands |
|
|
156
|
+
| `pushTimeoutMs` | 120000 | Deadline for `git push`, which waits on a remote |
|
|
157
|
+
| `messageMaxDiffBytes` | 65536 | Cap on the diff bytes handed to the model (truncated past it) |
|
|
158
|
+
| `messageLanguage` | `en` | Default commit-message language, `zh` or `en` |
|
|
159
|
+
| `productionBranch` | `main` | Production branch name; set `master` for older repos |
|
|
160
|
+
| `developBranch` | `develop` | Integration branch name |
|
|
161
|
+
| `featurePrefix` | `feature/` | Feature branch prefix |
|
|
162
|
+
| `releasePrefix` | `release/` | Release branch prefix |
|
|
163
|
+
| `hotfixPrefix` | `hotfix/` | Hotfix branch prefix |
|
|
164
|
+
| `tagPrefix` | `v` | Tag prefix applied when a release or hotfix finishes |
|
|
165
|
+
|
|
166
|
+
The commit panel carries an `English | 中文` switch beside "Draft message". It overrides `messageLanguage` for that call and remembers the choice in `localStorage`, so English stays the default until you pick otherwise.
|
|
167
|
+
|
|
168
|
+
## Git flow
|
|
169
|
+
|
|
170
|
+
The bottom two rows of the branch menu start and finish standard git-flow branches: features and releases branch off `develop`,
|
|
171
|
+
hotfixes branch off production, and a finish merges back with `--no-ff`.
|
|
172
|
+
|
|
173
|
+
| Type | Branches off | Merges into | Tag + version bump |
|
|
174
|
+
|---|---|---|---|
|
|
175
|
+
| `feature/` | `develop` | `develop` | no |
|
|
176
|
+
| `release/` | `develop` | production + `develop` | yes |
|
|
177
|
+
| `hotfix/` | production | production + `develop` | yes |
|
|
178
|
+
|
|
179
|
+
A finish runs: merge, tag, commit the `package.json` `version`, push the branches and tag, delete the local branch.
|
|
180
|
+
|
|
181
|
+
- When `develop` does not exist yet, "Start" offers to create it from production (or the current branch).
|
|
182
|
+
- Merges, tags, and version commits are not rolled back; a failed push reports "finished locally, but the push failed" and keeps the local result.
|
|
183
|
+
- Everything that can be refused is checked before the first write: a dirty worktree, an existing tag, a missing target branch.
|
|
184
|
+
- A merge conflict is left in place for you to resolve; nothing is aborted on your behalf.
|
|
185
|
+
- A repository without a root `package.json` skips the version step instead of failing.
|
|
186
|
+
|
|
187
|
+
## Security boundaries
|
|
188
|
+
|
|
189
|
+
- The browser sends only a `sessionId`. The host resolves the repository from that session's workspace, so a page can never name a directory for git to run in.
|
|
190
|
+
- Arguments are always an array, never a shell string, with `stdin: 'ignore'` and `GIT_TERMINAL_PROMPT=0`, so a credential prompt can never hang a call.
|
|
191
|
+
- `push` never passes `--force` or `--force-with-lease`, and only pushes the current branch's upstream (with `--set-upstream <remote> HEAD:refs/heads/<branch>` when it has none).
|
|
192
|
+
- Commits stage only the files checked in the panel. An empty selection answers `git/no-files-selected` and never falls back to an implicit `git add -A`.
|
|
193
|
+
- Switching branches on a dirty worktree fails by default (`git/dirty-worktree`). Only the user's second confirmation in the dialog sends `--force`.
|
|
194
|
+
- Discarding runs only after the destructive action is picked in a confirmation dialog: tracked paths go back to HEAD with `git restore --source=HEAD --staged --worktree`, untracked paths are removed with `git clean -fd` (no `-x`, so ignored files stay), and a conflicted path is refused rather than guessed at.
|
|
195
|
+
- `workspaceRegistry` is read lazily through `ctx.get('workspaceRegistry')` instead of being declared in `static inject`, so a profile without one (the shipped `headless` profile) still activates this entry rather than leaving a permanently pending plugin; every git call there answers `git/not-a-repository`.
|
|
196
|
+
- A missing git, a non-repository workspace, a timeout, and every refusal travel as structured codes (`git/not-installed`, `git/not-a-repository`, `git/timeout`, …) that the UI renders as copy. Outside a repository — or without git — the chip renders nothing at all.
|
|
197
|
+
|
|
198
|
+
## Layout
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
src/
|
|
202
|
+
├── index.ts host entry (default-exports the GitFlow service)
|
|
203
|
+
├── service.ts GitFlow: status / branches / checkout / createBranch / commit / discard / push / pull / generateMessage
|
|
204
|
+
│ + gitflowState / startFlow / initDevelop / finishFlow
|
|
205
|
+
├── git.ts GitRunner: subprocess + scrubbed env + timeout + porcelain v2 parsing
|
|
206
|
+
├── routes.ts /api/dsh-git-flow/* (registered only where a webServer exists)
|
|
207
|
+
├── message.ts commit message: ctx.llm.stream on the session's own provider/model, template fallback
|
|
208
|
+
├── contract.ts host↔browser types only
|
|
209
|
+
└── client/
|
|
210
|
+
├── index.ts slots.inject('conversation.input.left') + locale registration
|
|
211
|
+
├── BranchChip.tsx container: status, menu, dialogs, toast
|
|
212
|
+
├── BranchMenu.tsx the chip and its anchored menu
|
|
213
|
+
├── CommitDialog.tsx commit panel / new branch / forced-switch and discard confirmations
|
|
214
|
+
├── GitFlowDialog.tsx start a flow branch / guided develop creation / finish a flow branch
|
|
215
|
+
├── api.ts fetch wrapper + error codes to copy
|
|
216
|
+
├── styles.ts the few CSS rules the popover width and file-row ellipsis need
|
|
217
|
+
└── locales.ts zh/en dictionaries (en is typed against zh, so a missing key is a compile error)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Build
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
pnpm install
|
|
224
|
+
pnpm build # tsc -> lib/types/**, tsdown -> lib/index.js + lib/client.js
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**`lib/` is committed to the repository** (it is not in `.gitignore`) and the package no longer carries a `prepare` script, so installing from the git URL needs no build, no dev dependencies, and no entry in pnpm's `onlyBuiltDependencies` allowlist:
|
|
228
|
+
|
|
229
|
+
- pnpm 10.34+ blocks a git dependency's `prepare` (`ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`), and the allowlist entry it demands is `dsh-git-flow@<spec>#<commit sha>` — a string that goes stale on every upstream commit;
|
|
230
|
+
- shipping the build instead makes `dsh plugin add <spec>` take `lib/` as-is: measured at 263 ms under pnpm 10.34.5.
|
|
231
|
+
|
|
232
|
+
After editing `src/`, run **`pnpm build` and commit `lib/` along with it**, or everyone installing from git gets the previous build.
|
|
233
|
+
|
|
234
|
+
Artifact contract:
|
|
235
|
+
|
|
236
|
+
- `lib/index.js`: Node ESM, imported directly by the cordis loader.
|
|
237
|
+
- `lib/client.js`: a CJS factory wrapped as `window.__ModuleLoader__.load({ id: "dsh-git-flow", factory: (require) => … })`; `react`, `react/jsx-runtime`, and `@deepseek-ai/dsh-client-ui-primitives` stay external and come from the page's static module table.
|
|
238
|
+
|
|
239
|
+
## Compatibility
|
|
240
|
+
|
|
241
|
+
Developed and verified against `@deepseek-ai/dsh@0.1.6-alpha.2`, with peer and dev dependencies pinned to it. dsh is still alpha, so contracts such as `conversation.input.left`, `ctx.webServer.register`, and `ctx.slots.inject` may drift between minor versions. After upgrading dsh, rebuild and confirm the first line of `lib/client.js` is still `window.__ModuleLoader__.load({ id: "dsh-git-flow"`.
|
|
242
|
+
|
|
243
|
+
## Known limits
|
|
244
|
+
|
|
245
|
+
- An AI commit message needs one turn already sent in the session (the provider/model recorded in `request/header`). Without a route the panel answers `git/no-route` and tells you to send one message first; when the model fails or returns nothing usable, the `chore: update N files` template fills in and says so.
|
|
246
|
+
- Messages default to English. The panel's language switch (remembered per browser) and the `messageLanguage` config are the only two inputs; the template fallback follows the same choice.
|
|
247
|
+
- Remote branches appear only once `refs/remotes/**` exists locally (the plugin never fetches in the background); "Refresh status" makes no network call.
|
|
248
|
+
- Conflicted files cannot be checked; resolve the conflict in the conversation first.
|
|
249
|
+
- `git switch --force` discards **all** local changes, not just the conflicting ones — the confirmation copy says exactly that. To drop a single file's changes instead, use that file's Discard action in the commit panel.
|
|
250
|
+
- Switching to a ref that exists nowhere fails inside git and is reported as `git/failed` with git's own text. Only stale UI state can reach that path, since the menu lists branches that exist.
|
|
251
|
+
|
|
252
|
+
## Verification (0.1.6-alpha.2)
|
|
253
|
+
|
|
254
|
+
Measured on an isolated `DSH_HOME` with profile `gitflow-web` (`dsh-git-flow` appended to `dsh.profile.bundles`): the chip text equals `git branch --show-current`; the menu lists every local branch plus each remote-only one; search filters rows; switching moves both the chip and the worktree files; a dirty switch is refused and only confirms `--force` after a second dialog; an invalid branch name errors while a valid one is created and switched to; with a single file checked, the model returned a `feat: …` message and `git log -1 --name-only` showed only that file; commit-and-push made the branch appear in `git ls-remote` with its upstream set; a session whose workspace is not a repository renders no chip; the `headless` profile boots with no warning and no routes.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# dsh-git-flow
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
DeepSeek Harness 工作区 Git 插件:在输入框工具行放一个分支胶囊,点开能看分支、切分支、新建分支,并在面板里勾选文件提交 —— 提交信息由当前会话的模型根据改动自动生成。
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
输入框工具行: [⑂ feature/login 3] ← 点击(旁边有 Feature 类型徽标)
|
|
9
|
+
├ 搜索分支
|
|
10
|
+
├ 本地分支 · 2 ✓ feature/login
|
|
11
|
+
│ main
|
|
12
|
+
├ 新建分支…
|
|
13
|
+
├ 提交… (有改动时可用)
|
|
14
|
+
├ 刷新状态
|
|
15
|
+
├ 启动 Git Flow 分支…
|
|
16
|
+
└ 完成 Git Flow 分支… (在 flow 分支上且工作区干净时可用)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 安装
|
|
20
|
+
|
|
21
|
+
两种方式,任选其一。
|
|
22
|
+
|
|
23
|
+
### 方式一:插件页(GUI)
|
|
24
|
+
|
|
25
|
+
界面左侧「插件」→ 右上「添加插件」→ 「包名或地址」填:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
@hunan36/dsh-git-flow
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
点「安装」,装好后在「已安装」里启用(立即启用)。插件页会在**它自己所在的 profile**(通常是 `web`)里执行 `pnpm add` 并把本包写进 `dsh.profile.bundles`,两条都不用你手动做。
|
|
32
|
+
|
|
33
|
+
也可以填 GitHub 地址 `https://github.com/hunan36/dsh-git-flow`,但 `pnpm` 会把依赖锁到当时的那个 commit,之后**不会自动追新**。走 npm 包名才能比较版本、更新到最新。
|
|
34
|
+
|
|
35
|
+
装完**重启一次 `dsh web`**(宿主半的改动不会热更新;前端 bundle 会在重启后重新拉取)。
|
|
36
|
+
|
|
37
|
+
### 方式二:命令行(推荐用独立 profile)
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# 1. 用一个独立 profile,别动现有 web profile
|
|
41
|
+
dsh --profile gitflow-web --from-default-profile web
|
|
42
|
+
|
|
43
|
+
# 2. 装本插件 —— 给 npm 包名,或给 GitHub 地址 / 本地克隆目录的绝对路径
|
|
44
|
+
dsh plugin --profile gitflow-web add @hunan36/dsh-git-flow
|
|
45
|
+
# dsh plugin --profile gitflow-web add https://github.com/hunan36/dsh-git-flow
|
|
46
|
+
# dsh plugin --profile gitflow-web add "$PWD"
|
|
47
|
+
|
|
48
|
+
# 3. 插件页「立即启用」,或直接在 profile 的 package.json 里加 bundle
|
|
49
|
+
# dsh.profile.bundles += "@hunan36/dsh-git-flow"
|
|
50
|
+
# 由于本包声明了 dsh.bundle.patch,启用后它的 cordis.patch.yml 会自动挂载 GitFlow 服务
|
|
51
|
+
|
|
52
|
+
# 4. 起服务
|
|
53
|
+
dsh --profile gitflow-web
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
headless profile 也能装,但没有 `webServer`,`/api/dsh-git-flow/*` 不会注册,浏览器半也不存在 —— 不会报错。
|
|
57
|
+
|
|
58
|
+
## 更新
|
|
59
|
+
|
|
60
|
+
DSH 不为第三方插件提供自动更新,需要手动执行一次:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pnpm --dir ~/.dsh/profiles/web add @hunan36/dsh-git-flow@latest
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
然后**重启 `dsh web`**。宿主半(服务与 API 路由)的改动必须重启才生效;浏览器半在重启后会重新拉取。
|
|
67
|
+
|
|
68
|
+
查当前版本与最新版本:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npm view @hunan36/dsh-git-flow version
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
装 GitHub 地址的用户不会被自动追新(`pnpm` 已锁到具体 commit),更新时要显式重装:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pnpm --dir ~/.dsh/profiles/web add github:hunan36/dsh-git-flow#main
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 机器上没有 pnpm
|
|
81
|
+
|
|
82
|
+
dsh 的插件管理(插件页的「安装」和 `dsh plugin …`)底层调用 pnpm,机器上没有时会直接失败:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
dsh: pnpm was not found; install pnpm and make it available on PATH.
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
装一个就好(Node 自带 npm):
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
npm install -g pnpm # 装最新的 10.x
|
|
92
|
+
# 或者:corepack enable && corepack prepare pnpm@10 --activate
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
注意别让 Corepack 用它的默认版本 —— `corepack enable` 之后直接跑拿到的是 pnpm **8.15.7**,会踩下面那个 `ERR_PNPM_ADDING_TO_ROOT`。
|
|
96
|
+
|
|
97
|
+
不想装 pnpm 也有两条路,插件本身不依赖它(包内 `dependencies` 为空):
|
|
98
|
+
|
|
99
|
+
- **用 Node 自带的 npm**:`cd ~/.dsh/profiles/<profile> && npm install https://github.com/hunan36/dsh-git-flow`
|
|
100
|
+
- **直接拷贝**:把整个 `dsh-git-flow` 目录(含 `lib/`)拷进 `<profile>/node_modules/`,适用于离线或纯手工环境
|
|
101
|
+
|
|
102
|
+
这两种方式都必须**再补一步**:把 `dsh-git-flow` 加进该 profile `package.json` 的 `dsh.profile.bundles`,然后重启 `dsh web` —— 插件页和 `dsh plugin add` 会自动做这一步,手工装则要自己写,否则包在 `node_modules` 里也不会被加载。
|
|
103
|
+
|
|
104
|
+
### 让插件页改用 npm(机器上只有 npm)
|
|
105
|
+
|
|
106
|
+
dsh 的插件页默认执行 `pnpm add <地址>`,但插件管理器的命令是可配置的 —— 在 profile 的 `cordis.patch.yml` 里改成 `npm`,插件页就会用 npm 安装:
|
|
107
|
+
|
|
108
|
+
```yaml
|
|
109
|
+
# ~/.dsh/profiles/<profile>/cordis.patch.yml
|
|
110
|
+
- id: plugin-manager
|
|
111
|
+
config:
|
|
112
|
+
pnpmCommand: npm
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
之后按方式一正常操作(填地址 → 安装 → 立即启用)即可。命令行的 `dsh plugin … add` **不吃**这个配置,仍然需要 pnpm。
|
|
116
|
+
|
|
117
|
+
三点提醒:
|
|
118
|
+
|
|
119
|
+
- 这属于 dsh 的内部配置项,随版本可能变动;机器上能装 pnpm 时,优先装 pnpm。
|
|
120
|
+
- dsh 传给它的参数是 `add <spec>` / `remove <name>` / `view <spec> … --json`,npm 都支持;但 pnpm 专有语义(`-w`、`onlyBuiltDependencies`、lockfile)在 npm 下不存在,**同一个 profile 别混用两种管理器**。
|
|
121
|
+
- 界面与日志里那条命令仍会显示成 `pnpm add …`(dsh 的日志标题是写死的),判断实际用的是谁,看 profile 目录里留的是 `package-lock.json` 还是 `pnpm-lock.yaml`。
|
|
122
|
+
|
|
123
|
+
### 安装报错:ERR_PNPM_ADDING_TO_ROOT
|
|
124
|
+
|
|
125
|
+
dsh 的 profile 目录本身就是一个 pnpm workspace(`pnpm-workspace.yaml` 里 `packages: [.]`),而 pnpm 8 会把 `pnpm add` 当成「往 workspace 根加依赖」直接拒绝。profile 的 `package.json` 没有 `packageManager` 字段时,Node 自带的 Corepack 会自动补一个(常见是 `pnpm@8.15.7`),于是安装卡在这个检查上 —— 它发生在 pnpm 解析本插件**之前**,与本插件无关。
|
|
126
|
+
|
|
127
|
+
四种走法任选其一:
|
|
128
|
+
|
|
129
|
+
1. **插件页(GUI)安装**:那里的命令是 `pnpm add <地址>`,加不了 `-w` —— 先做第 2 步写 `.npmrc`,再点对话框里的「重试」即可(pnpm 8 下实测通过)。
|
|
130
|
+
2. 让这个 profile 接受根依赖,之后照常按文档安装:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
echo 'ignore-workspace-root-check=true' >> ~/.dsh/profiles/gitflow-web/.npmrc
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
3. 命令行安装时透传 `-w`(dsh 会把多余参数原样转发给 pnpm):
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
dsh plugin --profile gitflow-web add -w https://github.com/hunan36/dsh-git-flow.git
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
4. 把这个 profile 切到 pnpm 10(本插件实测版本):在 profile 目录执行 `corepack use pnpm@10`,或把 Corepack 补进 `package.json` 的 `packageManager` 改成 `pnpm@10.22.0`。
|
|
143
|
+
|
|
144
|
+
注意第一次失败后,Corepack 已经把 `packageManager: pnpm@8.15.7+sha512…` 写进了该 profile 的 `package.json`,之后这个 profile 的所有插件操作都会用 pnpm 8 —— 用第 4 步把它改掉最省事。
|
|
145
|
+
|
|
146
|
+
前置版本:Node `^22.19.0 || >=24.0.0`、pnpm 10;本仓库已用 `packageManager: pnpm@10.22.0` 声明。
|
|
147
|
+
|
|
148
|
+
## 配置
|
|
149
|
+
|
|
150
|
+
`cordis.patch.yml` 里可改:
|
|
151
|
+
|
|
152
|
+
| 键 | 默认 | 说明 |
|
|
153
|
+
|---|---|---|
|
|
154
|
+
| `timeoutMs` | 15000 | 只读/本地 git 命令超时 |
|
|
155
|
+
| `pushTimeoutMs` | 120000 | `git push` 超时(等远端) |
|
|
156
|
+
| `messageMaxDiffBytes` | 65536 | 喂给模型的 diff 字节上限,超出截断 |
|
|
157
|
+
| `messageLanguage` | `en` | 提交信息默认语言,`zh` / `en` |
|
|
158
|
+
| `productionBranch` | `main` | 生产分支名;老仓库用 `master` 时改这里 |
|
|
159
|
+
| `developBranch` | `develop` | 集成分支名 |
|
|
160
|
+
| `featurePrefix` | `feature/` | 功能分支前缀 |
|
|
161
|
+
| `releasePrefix` | `release/` | 发布分支前缀 |
|
|
162
|
+
| `hotfixPrefix` | `hotfix/` | 热修复分支前缀 |
|
|
163
|
+
| `tagPrefix` | `v` | 发布/热修复打完合并后打的标签前缀 |
|
|
164
|
+
|
|
165
|
+
提交面板的「生成提交信息」旁有 `English | 中文` 切换,它覆盖本次调用的 `messageLanguage`,并把选择记在 `localStorage`——
|
|
166
|
+
不选就一直默认英文。
|
|
167
|
+
|
|
168
|
+
## Git Flow
|
|
169
|
+
|
|
170
|
+
胶囊菜单最下方的「启动 Git Flow 分支」「完成 Git Flow 分支」按标准 git flow 走:功能与发布分支从 `develop` 切出,
|
|
171
|
+
热修复从生产分支切出;完成后 `--no-ff` 合并回去。
|
|
172
|
+
|
|
173
|
+
| 类型 | 从哪切出 | 完成后合并到 | 打标签 + 改版本号 |
|
|
174
|
+
|---|---|---|---|
|
|
175
|
+
| `feature/` | `develop` | `develop` | 否 |
|
|
176
|
+
| `release/` | `develop` | 生产分支 + `develop` | 是 |
|
|
177
|
+
| `hotfix/` | 生产分支 | 生产分支 + `develop` | 是 |
|
|
178
|
+
|
|
179
|
+
完成顺序:合并 → 打标签 → 提交 `package.json` 的 `version` → 推送分支与标签 → 删除本地分支。
|
|
180
|
+
|
|
181
|
+
- 仓库还没有 `develop` 时,「启动」会引导一键从生产分支(或当前分支)创建。
|
|
182
|
+
- 合并、打标签、改版本号这些完成后不会自动回滚;推送失败会明确提示「本地已完成,但推送失败」,本地成果保留。
|
|
183
|
+
- 能提前拦下的失败都在任何写操作之前检查:工作区不干净、标签已存在、目标分支不存在。
|
|
184
|
+
- 合并冲突时保留冲突现场让你手动解决,不自动 `--abort` 丢弃改动。
|
|
185
|
+
- 仓库根目录没有 `package.json`(非 Node 项目)时自动跳过改版本号这一步。
|
|
186
|
+
|
|
187
|
+
## 安全边界
|
|
188
|
+
|
|
189
|
+
- 浏览器只发 `sessionId`,仓库目录由宿主从会话的工作区解析,页面无法指定任意路径执行 git。
|
|
190
|
+
- 参数永远是数组,从不拼 shell 字符串;`stdin: 'ignore'`,`GIT_TERMINAL_PROMPT=0`,不会卡在凭据提示上。
|
|
191
|
+
- `push` 永不带 `--force` / `--force-with-lease`,只推当前分支的上游(无上游时 `--set-upstream <remote> HEAD:refs/heads/<branch>`)。
|
|
192
|
+
- 提交只 `git add` 面板里勾选的文件;一个都没勾返回 `git/no-files-selected`,绝不隐式 `git add -A`。
|
|
193
|
+
- 脏工作区切分支默认失败(`git/dirty-worktree`),只有用户在二次确认框里点「强制切换」才带 `--force`。
|
|
194
|
+
- 放弃更改只在确认框里点了「放弃更改」才执行:已跟踪路径用 `git restore --source=HEAD --staged --worktree` 回到 HEAD,未跟踪路径用 `git clean -fd` 删除(不带 `-x`,被忽略的文件不动);有冲突的文件直接拒绝,不做猜测。
|
|
195
|
+
- `workspaceRegistry` 用 `ctx.get('workspaceRegistry')` 惰性读取,而不是写进 `static inject`:headless 之类的 profile 没有它,
|
|
196
|
+
插件仍然激活(不会留下 "entry did not activate" 警告),此时任何 git 调用返回 `git/not-a-repository`。
|
|
197
|
+
- 没装 git、不是仓库、超时、以及每一次拒绝都走结构化错误码(`git/not-installed` / `git/not-a-repository` / `git/timeout` / …),
|
|
198
|
+
UI 显示对应文案;非仓库或无 git 时胶囊直接不渲染。
|
|
199
|
+
|
|
200
|
+
## 结构
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
src/
|
|
204
|
+
├── index.ts 宿主半入口(默认导出 GitFlow 服务)
|
|
205
|
+
├── service.ts GitFlow:status / branches / checkout / createBranch / commit / discard / push / pull / generateMessage
|
|
206
|
+
│ + gitflowState / startFlow / initDevelop / finishFlow
|
|
207
|
+
├── git.ts GitRunner:subprocess + 净化 env + 超时 + porcelain v2 解析
|
|
208
|
+
├── routes.ts /api/dsh-git-flow/*(仅挂载在有 webServer 的 profile)
|
|
209
|
+
├── message.ts 提交信息:ctx.llm.stream 走会话自己的 provider/model,失败退模板
|
|
210
|
+
├── contract.ts 宿主↔浏览器共享的纯类型
|
|
211
|
+
└── client/
|
|
212
|
+
├── index.ts slots.inject('conversation.input.left') + locale 注册
|
|
213
|
+
├── BranchChip.tsx 状态、菜单、弹窗、toast 的容器
|
|
214
|
+
├── BranchMenu.tsx 分支胶囊 + 锚定菜单
|
|
215
|
+
├── CommitDialog.tsx 提交面板 / 新建分支 / 强制切换与放弃更改的确认
|
|
216
|
+
├── GitFlowDialog.tsx 启动 flow 分支 / 引导创建 develop / 完成 flow 分支
|
|
217
|
+
├── api.ts fetch 包装 + 错误码到文案
|
|
218
|
+
├── styles.ts 弹层宽度、文件行省略所需的少量 CSS(唯一需要真 CSS 的地方)
|
|
219
|
+
└── locales.ts zh/en 字典(en 按 zh 收窄,缺键即编译错误)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## 构建
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
pnpm install
|
|
226
|
+
pnpm build # tsc -> lib/types/**,tsdown -> lib/index.js + lib/client.js
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**`lib/` 随仓库一起提交**(不在 `.gitignore` 里),包内也不再有 `prepare` 脚本 —— 这样从 git 地址安装时不需要构建、不需要 devDependencies,也不需要把包加进 pnpm 的 `onlyBuiltDependencies` 白名单:
|
|
230
|
+
|
|
231
|
+
- pnpm 10.34+ 会拦截 git 依赖的 `prepare`(`ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`),而白名单必须写成 `dsh-git-flow@<地址>#<commit sha>` 这种**带 sha 的精确串**,上游每次提交都会失效;
|
|
232
|
+
- 因此改成「产物入库、装上即用」:`dsh plugin add <地址>` 直接拿 `lib/`,实测 pnpm 10.34.5 下 263ms 装完。
|
|
233
|
+
|
|
234
|
+
改完 `src/` 之后请**先 `pnpm build` 再提交**,把 `lib/` 一起带上(否则别人装到的是旧产物)。
|
|
235
|
+
|
|
236
|
+
产物契约:
|
|
237
|
+
|
|
238
|
+
- `lib/index.js`:Node ESM,被 cordis loader 直接 import。
|
|
239
|
+
- `lib/client.js`:CJS 工厂包成 `window.__ModuleLoader__.load({ id: "dsh-git-flow", factory: (require) => … })`;
|
|
240
|
+
`react`、`react/jsx-runtime`、`@deepseek-ai/dsh-client-ui-primitives` 保持 external,由页面静态模块表提供。
|
|
241
|
+
|
|
242
|
+
## 兼容性
|
|
243
|
+
|
|
244
|
+
针对 `@deepseek-ai/dsh@0.1.6-alpha.2` 实测开发,peer/devDependencies 锁在该版本。dsh 尚在 alpha,
|
|
245
|
+
`conversation.input.left`、`ctx.webServer.register`、`ctx.slots.inject` 等契约可能在小版本间漂移;
|
|
246
|
+
升级 dsh 后请重跑构建并确认 `lib/client.js` 首行仍是 `window.__ModuleLoader__.load({ id: "dsh-git-flow"`。
|
|
247
|
+
|
|
248
|
+
## 已知边界
|
|
249
|
+
|
|
250
|
+
- AI 提交信息要求会话已经有过一轮对话(`request/header` 里记了 provider/model)。没有路由时返回 `git/no-route`,面板提示先发一轮消息;模型失败或输出不合规时用模板兜底(英文 `chore: update N files` / 中文 `chore: 更新 N 个文件`),且会明确告知。
|
|
251
|
+
- 提交信息默认英文。只有两个入口能改:面板里的语言切换(按浏览器记住)和 `messageLanguage` 配置;模板兜底跟随同一个选择。
|
|
252
|
+
- 远端分支只在本地已有 `refs/remotes/**` 时才可见(插件不做后台 fetch);「刷新状态」不触发网络。
|
|
253
|
+
- 冲突文件不可勾选,需要先在对话里解决冲突。
|
|
254
|
+
- `git switch --force` 会丢弃**全部**本地改动(不只是冲突的那些),确认框里的文案就是这么写的;只想丢掉某一个文件的改动,用提交面板里该文件的「放弃更改」。
|
|
255
|
+
- 切到一个不存在且无同名远端的引用时,git 自己会失败,条目按 `git/failed` 上报 git 原文(这种情况只能来自过期的 UI 状态,正常菜单里列的都是真实存在的分支)。
|
|
256
|
+
|
|
257
|
+
## 验证记录(0.1.6-alpha.2)
|
|
258
|
+
|
|
259
|
+
独立 `DSH_HOME` + profile `gitflow-web`(bundles 追加 `dsh-git-flow`)实测:胶囊文本 == `git branch --show-current`;
|
|
260
|
+
弹层行数 == 本地分支 + 仅在远端;搜索过滤;切分支后胶囊与工作区文件同步;脏工作区切换被拦下并弹二次确认(`--force` 才丢弃改动);
|
|
261
|
+
非法分支名报错、合法名创建并切换;勾选单个文件生成 AI 信息(`feat: …`)并只提交该文件(`git log -1 --name-only` 验证);
|
|
262
|
+
提交并推送后 `git ls-remote` 出现该分支且建立 upstream;非 git 工作区会话里胶囊不渲染;`headless` profile 启动无警告、无路由。
|
package/cordis.patch.yml
ADDED