dsh-tiddlywiki 0.27.2 → 0.28.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 -21
- package/README.md +3 -0
- package/cordis.patch.yml +13 -13
- package/docs/CHANGELOG.md +2 -1
- package/docs/code-review-2026-09-17.md +151 -0
- package/docs/features.md +5 -2
- package/docs/usage.md +34 -3
- package/docs/wiki-split.md +97 -0
- package/lib/client.js +54 -27
- package/lib/index.js +2803 -517
- package/lib/index.js.map +1 -1
- package/package.json +5 -4
- package/skills/wiki-split/SKILL.md +106 -0
- package/src/client/endpoints.ts +37 -0
- package/src/client/index.ts +14 -0
- package/src/client/knowledge-fab.ts +62 -3
- package/src/client/markdown-editor.ts +128 -128
- package/src/client/panel.ts +2 -1
- package/src/client/settings-page.ts +217 -3
- package/src/client/state.ts +39 -39
- package/src/client/status-cache.ts +17 -0
- package/src/client/styles.ts +27 -0
- package/src/client/toast.ts +22 -22
- package/src/client/tw-frame.ts +28 -4
- package/src/client/wiki-focus.ts +70 -0
- package/src/client/wiki-scope-dock.ts +116 -0
- package/src/host/admin.ts +175 -35
- package/src/host/git.ts +50 -5
- package/src/host/path-key.ts +35 -0
- package/src/host/prompt.ts +24 -0
- package/src/host/repo-committers.ts +149 -0
- package/src/host/routes.ts +254 -81
- package/src/host/seeds.ts +51 -10
- package/src/host/session-scope.ts +154 -0
- package/src/host/skill-install.ts +121 -0
- package/src/host/tools.ts +141 -49
- package/src/host/wiki-farm.ts +417 -0
- package/src/host/wiki-instance.ts +596 -0
- package/src/host/wiki-registry.ts +601 -0
- package/src/host/wiki.ts +19 -0
- package/src/index.ts +686 -459
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ONE debounced auto-committer per REPOSITORY (v0.28.0).
|
|
3
|
+
*
|
|
4
|
+
* WHY NOT ONE PER WIKI
|
|
5
|
+
* --------------------
|
|
6
|
+
* Two knowledge bases may be subfolders of one repository (the "工作 + 个人 共用现有
|
|
7
|
+
* 仓库、按文件夹物理划分" topology). Git then has ONE index, and `git add -A` run
|
|
8
|
+
* from ANY subdirectory stages the WHOLE working tree — so two per-folder
|
|
9
|
+
* committers would each commit the other's changes, and one of them would
|
|
10
|
+
* forever report `nothing to commit`. The unit that can safely commit is the
|
|
11
|
+
* REPOSITORY, so that is the unit this class keys on.
|
|
12
|
+
*
|
|
13
|
+
* It also decides where the commit runs: at the REPOSITORY ROOT, never in a
|
|
14
|
+
* wiki subfolder (`commit(dir)` runs `git add -A` with `cwd = dir`).
|
|
15
|
+
*
|
|
16
|
+
* THE DIR → REPO LOOKUP IS ASYMMETRICALLY CACHED
|
|
17
|
+
* ----------------------------------------------
|
|
18
|
+
* A POSITIVE answer (this folder belongs to repo X) is cached forever. A
|
|
19
|
+
* NEGATIVE one is not cached at all: `git init` happens moments after the first
|
|
20
|
+
* touch in a fresh install, and a cached "not a repo" would silently disable
|
|
21
|
+
* auto-commit for the rest of the session — exactly the class of bug this
|
|
22
|
+
* repository has paid for more than once.
|
|
23
|
+
*
|
|
24
|
+
* @module dsh-tiddlywiki/host/repo-committers
|
|
25
|
+
*/
|
|
26
|
+
import { AutoCommitter, type GitFace } from './git.ts'
|
|
27
|
+
import { pathComparisonKey } from './path-key.ts'
|
|
28
|
+
|
|
29
|
+
/** The effective auto-commit settings (re-read so a settings save applies live). */
|
|
30
|
+
export interface RepoCommitSettings {
|
|
31
|
+
autoCommit: boolean
|
|
32
|
+
debounceMs: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface RepoCommittersOptions {
|
|
36
|
+
git: GitFace
|
|
37
|
+
/**
|
|
38
|
+
* The CURRENT effective settings FOR ONE REPOSITORY (v0.28.0).
|
|
39
|
+
*
|
|
40
|
+
* Per repository, not per wiki: several knowledge bases may share one
|
|
41
|
+
* repository, and their `git.*` would otherwise be read from whichever wiki
|
|
42
|
+
* happens to be the default — so the books repository could be auto-committed
|
|
43
|
+
* with the notes wiki's debounce (or not at all).
|
|
44
|
+
*/
|
|
45
|
+
settings: (repoRoot: string) => RepoCommitSettings
|
|
46
|
+
/** Commit message factory. */
|
|
47
|
+
message: () => string
|
|
48
|
+
log?: (message: string) => void
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface RepoState {
|
|
52
|
+
repoRoot: string
|
|
53
|
+
committer: AutoCommitter
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class RepoCommitters {
|
|
57
|
+
/** Keyed by `pathComparisonKey(repoRoot)`. */
|
|
58
|
+
private readonly repos = new Map<string, RepoState>()
|
|
59
|
+
/** Positive `dir → repoRoot` cache only (see the module header). */
|
|
60
|
+
private readonly dirToRepo = new Map<string, string>()
|
|
61
|
+
private readonly log: (message: string) => void
|
|
62
|
+
|
|
63
|
+
constructor(private readonly options: RepoCommittersOptions) {
|
|
64
|
+
this.log = options.log ?? ((message) => { console.warn('[dsh-tiddlywiki]', message) })
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Repositories with a live committer (diagnostics / tests). */
|
|
68
|
+
repoRoots(): string[] {
|
|
69
|
+
return [...this.repos.values()].map((state) => state.repoRoot)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The repository root owning `dir`, or undefined when it is not in one. */
|
|
73
|
+
async repoRootOf(dir: string): Promise<string | undefined> {
|
|
74
|
+
const key = pathComparisonKey(dir)
|
|
75
|
+
const cached = this.dirToRepo.get(key)
|
|
76
|
+
if (cached !== undefined) return cached
|
|
77
|
+
const root = await this.options.git.repoRoot(dir)
|
|
78
|
+
if (root !== undefined) this.dirToRepo.set(key, root)
|
|
79
|
+
return root
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Debounced commit for the repository owning `dir`.
|
|
84
|
+
*
|
|
85
|
+
* Fire-and-forget friendly (the fs watcher calls it synchronously): the first
|
|
86
|
+
* call for a folder pays one `git rev-parse`, every later one is a Map hit.
|
|
87
|
+
*/
|
|
88
|
+
async touch(dir: string): Promise<void> {
|
|
89
|
+
const state = await this.stateFor(dir)
|
|
90
|
+
state?.committer.touch()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Commit now. Omit `dir` to flush every repository (teardown / switch). */
|
|
94
|
+
async flush(dir?: string): Promise<void> {
|
|
95
|
+
if (dir === undefined) {
|
|
96
|
+
for (const state of this.repos.values()) {
|
|
97
|
+
try { await state.committer.flush() } catch { /* best-effort */ }
|
|
98
|
+
}
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
const root = await this.repoRootOf(dir)
|
|
102
|
+
if (root === undefined) return
|
|
103
|
+
try { await this.repos.get(pathComparisonKey(root))?.committer.flush() } catch { /* best-effort */ }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Rebuild every committer from the CURRENT settings (v0.25.0's "git.* takes
|
|
108
|
+
* effect on the running plugin"): `AutoCommitter` snapshots `enabled` and
|
|
109
|
+
* `debounceMs` at construction, so a settings-page save has to rebuild.
|
|
110
|
+
*/
|
|
111
|
+
rebuild(): void {
|
|
112
|
+
for (const [key, state] of [...this.repos]) {
|
|
113
|
+
state.committer.dispose()
|
|
114
|
+
this.repos.set(key, { repoRoot: state.repoRoot, committer: this.create(state.repoRoot) })
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Release every committer (plugin teardown). */
|
|
119
|
+
dispose(): void {
|
|
120
|
+
for (const state of this.repos.values()) state.committer.dispose()
|
|
121
|
+
this.repos.clear()
|
|
122
|
+
this.dirToRepo.clear()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private create(repoRoot: string): AutoCommitter {
|
|
126
|
+
const settings = this.options.settings(repoRoot)
|
|
127
|
+
return new AutoCommitter({
|
|
128
|
+
git: this.options.git,
|
|
129
|
+
// The COMMIT runs at the repository root, never in a wiki subfolder.
|
|
130
|
+
dir: repoRoot,
|
|
131
|
+
enabled: settings.autoCommit,
|
|
132
|
+
debounceMs: settings.debounceMs,
|
|
133
|
+
message: this.options.message,
|
|
134
|
+
onError: (err) => this.log(`autocommit(${repoRoot}): ${err instanceof Error ? err.message : String(err)}`),
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private async stateFor(dir: string): Promise<RepoState | undefined> {
|
|
139
|
+
const root = await this.repoRootOf(dir)
|
|
140
|
+
if (root === undefined) return undefined
|
|
141
|
+
const key = pathComparisonKey(root)
|
|
142
|
+
const existing = this.repos.get(key)
|
|
143
|
+
if (existing !== undefined) return existing
|
|
144
|
+
const state: RepoState = { repoRoot: root, committer: this.create(root) }
|
|
145
|
+
this.repos.set(key, state)
|
|
146
|
+
this.log(`自动提交按仓库分组:${root}`)
|
|
147
|
+
return state
|
|
148
|
+
}
|
|
149
|
+
}
|