@dmthepm/commune 0.1.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 +468 -0
- package/bin/commune.mjs +29 -0
- package/lib/cli/check.d.ts +13 -0
- package/lib/cli/check.js +58 -0
- package/lib/cli/errors.d.ts +29 -0
- package/lib/cli/errors.js +41 -0
- package/lib/cli/gate.d.ts +34 -0
- package/lib/cli/gate.js +165 -0
- package/lib/cli/main.d.ts +20 -0
- package/lib/cli/main.js +175 -0
- package/lib/cli/query.d.ts +30 -0
- package/lib/cli/query.js +103 -0
- package/lib/cli/related.d.ts +17 -0
- package/lib/cli/related.js +177 -0
- package/lib/cli/render.d.ts +20 -0
- package/lib/cli/render.js +32 -0
- package/lib/cli/root.d.ts +11 -0
- package/lib/cli/root.js +28 -0
- package/lib/cli/usage.d.ts +3 -0
- package/lib/cli/usage.js +46 -0
- package/lib/cli/version.d.ts +24 -0
- package/lib/cli/version.js +29 -0
- package/lib/integration.d.ts +24 -0
- package/lib/integration.js +111 -0
- package/lib/lib/graph.d.ts +354 -0
- package/lib/lib/graph.js +774 -0
- package/lib/markdown.d.ts +30 -0
- package/lib/markdown.js +24 -0
- package/lib/rehype-external-links.d.ts +15 -0
- package/lib/rehype-external-links.js +46 -0
- package/lib/remark-wikilinks.d.ts +25 -0
- package/lib/remark-wikilinks.js +108 -0
- package/package.json +101 -0
- package/src/components/Backlinks.astro +17 -0
- package/src/components/BacklinksScript.astro +117 -0
- package/src/components/Footer.astro +35 -0
- package/src/components/Header.astro +250 -0
- package/src/components/HeaderStarScript.astro +380 -0
- package/src/components/HomeFooterCards.astro +155 -0
- package/src/components/MarkdownLink.astro +36 -0
- package/src/components/PlausibleScript.astro +13 -0
- package/src/components/RelatedNotes.astro +77 -0
- package/src/components/SearchModal.astro +213 -0
- package/src/components/StarredLinksScript.astro +92 -0
- package/src/components/panes.ts +61 -0
- package/src/styles/design-system.css +157 -0
- package/src/styles/notes.css +85 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Devon Meadows
|
|
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
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
# Commune Wiki
|
|
2
|
+
|
|
3
|
+
An Astro wiki engine โ WikiLinks, sliding panes, backlinks, static search โ and a `commune` CLI that queries the content graph and checks links.
|
|
4
|
+
|
|
5
|
+
**License**: MIT ยท **Live example**: [devonmeadows.com](https://devonmeadows.com)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## โจ Features
|
|
10
|
+
|
|
11
|
+
- ๐ **WikiLinks**: `[[Note Title]]` automatically converts to links
|
|
12
|
+
- ๐ **Sliding Panes**: Andy Matuschak-style cascading note navigation
|
|
13
|
+
- ๐๏ธ **Hover Previews**: See note content on hover before clicking
|
|
14
|
+
- ๐ **Backlinks**: Auto-generated bidirectional link graph
|
|
15
|
+
- ๐จ **Design System**: Custom CSS variables with light/dark mode
|
|
16
|
+
- ๐ **Search**: Cmd-K palette with Pagefind static search
|
|
17
|
+
- ๐ **Markdown-First**: Git-backed content, version controlled
|
|
18
|
+
- ๐ **Fast**: Static site generation (no runtime database)
|
|
19
|
+
- ๐ฏ **Zero Config**: Works out of the box, customize as needed
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## ๐ฏ Who Is This For?
|
|
24
|
+
|
|
25
|
+
**Personal Knowledge Management**:
|
|
26
|
+
- Researchers building interconnected notes (Zettelkasten/Evergreen Notes)
|
|
27
|
+
- Writers managing drafts, research, and published content
|
|
28
|
+
- Developers documenting code, decisions, and learnings
|
|
29
|
+
- Anyone tired of silo'd notes in proprietary apps
|
|
30
|
+
|
|
31
|
+
**vs. Other Tools**:
|
|
32
|
+
| Tool | Approach | Commune Wiki |
|
|
33
|
+
|------|----------|--------------|
|
|
34
|
+
| Obsidian | Desktop app, proprietary sync | Web-first, self-hosted, MIT |
|
|
35
|
+
| Notion | Cloud SaaS, vendor lock-in | Git-backed, own your data |
|
|
36
|
+
| Roam | SaaS, $15/mo | Free, open source, MIT |
|
|
37
|
+
| Logseq | Local-first, complex setup | Simple Astro build, deploy anywhere |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## ๐ Quick Start
|
|
42
|
+
|
|
43
|
+
### Prerequisites
|
|
44
|
+
|
|
45
|
+
- Node.js 22.18+ and pnpm
|
|
46
|
+
|
|
47
|
+
### Install & Run
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Clone repository
|
|
51
|
+
git clone git@github.com:dmthepm/commune-wiki.git
|
|
52
|
+
cd commune-wiki
|
|
53
|
+
|
|
54
|
+
# Install dependencies
|
|
55
|
+
pnpm install
|
|
56
|
+
|
|
57
|
+
# Start dev server (http://localhost:4321)
|
|
58
|
+
pnpm dev
|
|
59
|
+
|
|
60
|
+
# Build for production
|
|
61
|
+
pnpm build
|
|
62
|
+
|
|
63
|
+
# Preview production build
|
|
64
|
+
pnpm preview
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Install
|
|
68
|
+
|
|
69
|
+
The engine is a package. Add it to an Astro 7 project:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pnpm add @dmthepm/commune
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
npm and yarn take the same line. The published tarball ships `lib/` already
|
|
76
|
+
compiled, so nothing builds on install, no build script needs approving, and a
|
|
77
|
+
consumer needs only Node 22.12+ โ Astro's own floor.[^git]
|
|
78
|
+
|
|
79
|
+
[^git]: **Before `v0.1.0` reaches npm, install from a git ref instead** โ
|
|
80
|
+
`pnpm add github:dmthepm/commune-wiki#<tag>` โ and pnpm consumers need one
|
|
81
|
+
extra line for it, because a git dependency arrives as source and compiles
|
|
82
|
+
itself in its `prepare` script. pnpm 10 refuses to run that unless your
|
|
83
|
+
project names the package, and without the entry the install fails with
|
|
84
|
+
`ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`. The details are folded below; all
|
|
85
|
+
of it goes away on the first npm publish, which is what this footnote is
|
|
86
|
+
counting down to.
|
|
87
|
+
|
|
88
|
+
<details>
|
|
89
|
+
<summary>Installing from a git ref, in full</summary>
|
|
90
|
+
|
|
91
|
+
```jsonc
|
|
92
|
+
// your package.json
|
|
93
|
+
{
|
|
94
|
+
"dependencies": {
|
|
95
|
+
"@dmthepm/commune": "github:dmthepm/commune-wiki#<tag>"
|
|
96
|
+
},
|
|
97
|
+
"pnpm": {
|
|
98
|
+
"onlyBuiltDependencies": ["@dmthepm/commune@github:dmthepm/commune-wiki#<tag>"]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Note the entry is `name@spec`, not the bare name โ a bare name approves a
|
|
104
|
+
package from the registry, and for a git dependency pnpm matches the whole
|
|
105
|
+
specifier, so a name on its own is silently not a match. It has to be the same
|
|
106
|
+
specifier you wrote in `dependencies`, which means it changes when you bump the
|
|
107
|
+
tag. The same entry works in `pnpm-workspace.yaml` if you keep pnpm settings
|
|
108
|
+
there.
|
|
109
|
+
|
|
110
|
+
**Older pnpm 10 wants the other spelling.** Around 10.19 the `name@spec` form is
|
|
111
|
+
rejected with `ERR_PNPM_INVALID_VERSION_UNION` ("Use exact versions only") and
|
|
112
|
+
the bare `"@dmthepm/commune"` is what works โ those releases also approve a git
|
|
113
|
+
dependency's build scripts on their own, so you may need nothing at all. Do not
|
|
114
|
+
guess which side of the line you are on: run the install and read the error.
|
|
115
|
+
pnpm prints the exact entry your version expects.
|
|
116
|
+
|
|
117
|
+
npm and yarn need nothing extra โ they run a git dependency's `prepare` without
|
|
118
|
+
asking.
|
|
119
|
+
|
|
120
|
+
</details>
|
|
121
|
+
|
|
122
|
+
### Create Your First Note
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Create a note in src/content/notes/
|
|
126
|
+
cat > src/content/notes/hello-world.md << 'MDEOF'
|
|
127
|
+
---
|
|
128
|
+
title: "Hello World"
|
|
129
|
+
visibility: "public"
|
|
130
|
+
status: "evergreen"
|
|
131
|
+
summary: "My first note"
|
|
132
|
+
tags: [getting-started]
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
Welcome to your personal wiki!
|
|
136
|
+
|
|
137
|
+
Link to other notes with [[Note Title]] syntax.
|
|
138
|
+
MDEOF
|
|
139
|
+
|
|
140
|
+
# Start dev server and visit http://localhost:4321
|
|
141
|
+
pnpm dev
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## ๐ Project Structure
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
commune-wiki/
|
|
150
|
+
โโโ src/
|
|
151
|
+
โ โโโ content/
|
|
152
|
+
โ โ โโโ config.ts # Content collection schemas
|
|
153
|
+
โ โ โโโ notes/ # Your markdown notes
|
|
154
|
+
โ โโโ components/
|
|
155
|
+
โ โ โโโ Header.astro # Site header
|
|
156
|
+
โ โ โโโ SearchModal.astro
|
|
157
|
+
โ โ โโโ Backlinks.astro
|
|
158
|
+
โ โโโ pages/
|
|
159
|
+
โ โ โโโ index.astro # Homepage
|
|
160
|
+
โ โ โโโ notes/
|
|
161
|
+
โ โ โโโ [...slug].astro # Note pages + pane logic
|
|
162
|
+
โ โโโ styles/
|
|
163
|
+
โ โโโ design-system.css # Custom CSS variables
|
|
164
|
+
โ โโโ notes.css # Note typography
|
|
165
|
+
โโโ public/
|
|
166
|
+
โ โโโ backlinks.json # Auto-generated backlinks graph
|
|
167
|
+
โโโ astro.config.mjs # Astro config + remark plugins
|
|
168
|
+
โโโ package.json
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## โ๏ธ Writing Notes
|
|
174
|
+
|
|
175
|
+
### Note Schema
|
|
176
|
+
|
|
177
|
+
Every note requires frontmatter:
|
|
178
|
+
|
|
179
|
+
```markdown
|
|
180
|
+
---
|
|
181
|
+
title: "Note Title"
|
|
182
|
+
visibility: "public" # public | private | draft
|
|
183
|
+
status: "evergreen" # seed | growing | evergreen
|
|
184
|
+
summary: "Brief description for previews"
|
|
185
|
+
tags: [tag1, tag2]
|
|
186
|
+
aliases: ["Short Name"]
|
|
187
|
+
updated: 2025-10-21
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
Your note content here with [[WikiLinks]] to other notes.
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Visibility**:
|
|
194
|
+
- `public` - Published to site (default: only public notes shown)
|
|
195
|
+
- `private` - Not published
|
|
196
|
+
- `draft` - Work in progress, not indexed
|
|
197
|
+
|
|
198
|
+
**Status**:
|
|
199
|
+
- `seed` - Early idea, needs development
|
|
200
|
+
- `growing` - Actively being refined
|
|
201
|
+
- `evergreen` - Well-developed, stable
|
|
202
|
+
|
|
203
|
+
### WikiLinks Syntax
|
|
204
|
+
|
|
205
|
+
```markdown
|
|
206
|
+
[[Note Title]] โ Links to note
|
|
207
|
+
[[Note Title|Display Text]] โ Custom text
|
|
208
|
+
[[Multi-word Note]] โ Normalized matching
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**How it works**:
|
|
212
|
+
1. Build-time plugin scans all notes
|
|
213
|
+
2. Creates title โ slug lookup index
|
|
214
|
+
3. Transforms `[[Title]]` to `<a href="/notes/slug/">`
|
|
215
|
+
4. Broken links render as plain text (not clickable)
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## ๐จ Customization
|
|
220
|
+
|
|
221
|
+
### Design System
|
|
222
|
+
|
|
223
|
+
Edit `src/styles/design-system.css`:
|
|
224
|
+
|
|
225
|
+
```css
|
|
226
|
+
:root {
|
|
227
|
+
--c-bg: #0a0a0b;
|
|
228
|
+
--c-accent: #8b7bff;
|
|
229
|
+
--c-text: #e8e6e3;
|
|
230
|
+
/* ... customize colors ... */
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
[data-theme="light"] {
|
|
234
|
+
--c-bg: #fafaf9;
|
|
235
|
+
/* ... light mode overrides ... */
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Typography
|
|
240
|
+
|
|
241
|
+
Edit `src/styles/notes.css` for note-specific styling (headings, lists, code blocks).
|
|
242
|
+
|
|
243
|
+
### Pane Behavior
|
|
244
|
+
|
|
245
|
+
Pane logic in `src/pages/notes/[...slug].astro`:
|
|
246
|
+
|
|
247
|
+
```javascript
|
|
248
|
+
// Customize pane behavior:
|
|
249
|
+
setupPanes() // Initialize
|
|
250
|
+
openPane(url) // Open new pane
|
|
251
|
+
closePane(pane) // Remove pane
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## ๐ Search
|
|
257
|
+
|
|
258
|
+
**Pagefind** generates a static search index at build time:
|
|
259
|
+
|
|
260
|
+
- No server required
|
|
261
|
+
- Instant client-side search
|
|
262
|
+
- Automatically indexes all public notes
|
|
263
|
+
- Cmd-K hotkey to open search modal
|
|
264
|
+
|
|
265
|
+
**Dev mode**: Falls back to backlinks.json when Pagefind not available.
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## ๐ Backlinks
|
|
270
|
+
|
|
271
|
+
Backlinks are auto-generated at build time via the `src/integration.ts` integration:
|
|
272
|
+
|
|
273
|
+
1. Scans all notes for WikiLinks
|
|
274
|
+
2. Creates bidirectional graph
|
|
275
|
+
3. Outputs to `public/backlinks.json` and `<outDir>/backlinks.json`
|
|
276
|
+
4. Displayed in `Backlinks.astro` component ("Links to this note")
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## ๐ Deployment
|
|
281
|
+
|
|
282
|
+
### Static Hosting (Recommended)
|
|
283
|
+
|
|
284
|
+
**Cloudflare Pages / Vercel / Netlify**:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
# Build command
|
|
288
|
+
pnpm build
|
|
289
|
+
|
|
290
|
+
# Output directory
|
|
291
|
+
dist/
|
|
292
|
+
|
|
293
|
+
# Deploy
|
|
294
|
+
# Connect GitHub repo, auto-deploy on push
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Self-Hosted (Caddy)
|
|
298
|
+
|
|
299
|
+
```yaml
|
|
300
|
+
# docker-compose.yml
|
|
301
|
+
caddy:
|
|
302
|
+
image: caddy:alpine
|
|
303
|
+
volumes:
|
|
304
|
+
- ./dist:/srv:ro
|
|
305
|
+
- ./Caddyfile:/etc/caddy/Caddyfile
|
|
306
|
+
ports:
|
|
307
|
+
- "80:80"
|
|
308
|
+
- "443:443"
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
```Caddyfile
|
|
312
|
+
# Caddyfile
|
|
313
|
+
yourdomain.com {
|
|
314
|
+
root * /srv
|
|
315
|
+
file_server
|
|
316
|
+
try_files {path} {path}/ /index.html
|
|
317
|
+
encode gzip
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Self-Hosted (Railway)
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
# Install Railway CLI
|
|
325
|
+
npm install -g railway
|
|
326
|
+
|
|
327
|
+
# Deploy
|
|
328
|
+
railway init
|
|
329
|
+
railway up
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Railway auto-detects Astro and builds with `pnpm build`.
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## ๐ ๏ธ Development
|
|
337
|
+
|
|
338
|
+
### Commands
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
pnpm dev # Start dev server (port 4321)
|
|
342
|
+
pnpm build # Build production site
|
|
343
|
+
pnpm preview # Preview production build
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Testing
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
# Run the test suite
|
|
350
|
+
pnpm test
|
|
351
|
+
|
|
352
|
+
# Check the content graph (broken links, duplicate names, ambiguous targets)
|
|
353
|
+
node bin/commune.mjs check --json
|
|
354
|
+
|
|
355
|
+
# Preview before deploying
|
|
356
|
+
pnpm preview
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Debugging WikiLinks
|
|
360
|
+
|
|
361
|
+
**Issue**: Links not working?
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
# Check cache consistency (should show same count each time)
|
|
365
|
+
pnpm build 2>&1 | grep "Lookup built with"
|
|
366
|
+
|
|
367
|
+
# Find broken links
|
|
368
|
+
pnpm build 2>&1 | grep "Broken link"
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## ๐ฆ Tech Stack
|
|
374
|
+
|
|
375
|
+
- **Astro** - Static site generator
|
|
376
|
+
- **Tailwind CSS** - Utility-first styling
|
|
377
|
+
- **Pagefind** - Static search index
|
|
378
|
+
- **remark-wikilinks** - WikiLink transformation plugin (custom, `src/remark-wikilinks.ts`)
|
|
379
|
+
- **No framework dependencies** - Vanilla JS for interactivity
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## ๐ Documentation
|
|
384
|
+
|
|
385
|
+
**For Contributors**:
|
|
386
|
+
- Architecture details in original README (check git history)
|
|
387
|
+
- Pane system implementation in `src/pages/notes/[...slug].astro`
|
|
388
|
+
- WikiLink plugin in `src/remark-wikilinks.ts`
|
|
389
|
+
- Backlinks integration in `src/integration.ts`
|
|
390
|
+
|
|
391
|
+
**For Users**:
|
|
392
|
+
- This README covers installation and usage
|
|
393
|
+
- See [devonmeadows.com](https://devonmeadows.com) for live example
|
|
394
|
+
- Issues/questions: [GitHub Issues](https://github.com/dmthepm/commune-wiki/issues)
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## ๐ค Contributing
|
|
399
|
+
|
|
400
|
+
This is an open-source project under the MIT License. Contributions welcome!
|
|
401
|
+
|
|
402
|
+
**How to contribute**:
|
|
403
|
+
1. Fork the repository
|
|
404
|
+
2. Create a feature branch (`git checkout -b feature/your-feature`)
|
|
405
|
+
3. Make changes and test locally (`pnpm dev`)
|
|
406
|
+
4. Build to verify (`pnpm build`)
|
|
407
|
+
5. Commit with clear message
|
|
408
|
+
6. Push and create Pull Request
|
|
409
|
+
|
|
410
|
+
**Areas for contribution**:
|
|
411
|
+
- [ ] Automated tests (Puppeteer or Playwright)
|
|
412
|
+
- [ ] Additional themes/design systems
|
|
413
|
+
- [ ] Search improvements (fuzzy matching, ranking)
|
|
414
|
+
- [ ] Graph visualization of backlinks
|
|
415
|
+
- [ ] Mobile responsiveness improvements
|
|
416
|
+
- [ ] Performance optimizations
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## ๐ Known Issues
|
|
421
|
+
|
|
422
|
+
### WikiLink Cache Bug (FIXED)
|
|
423
|
+
|
|
424
|
+
**Symptom**: Links only work on last note built.
|
|
425
|
+
|
|
426
|
+
**Fix**: Ensure cache size check in `src/remark-wikilinks.ts`:
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
if (notesCache && notesCache.size > 0) { // MUST check .size!
|
|
430
|
+
return buildFromCache();
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Pane Styling Not Applied
|
|
435
|
+
|
|
436
|
+
**Symptom**: Panes don't stack correctly.
|
|
437
|
+
|
|
438
|
+
**Fix**: Use `<style is:global>` in `[...slug].astro` for dynamic panes.
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## ๐ License
|
|
443
|
+
|
|
444
|
+
MIT - See [LICENSE](LICENSE) file.
|
|
445
|
+
|
|
446
|
+
**What this means**:
|
|
447
|
+
- Free to use, modify, distribute, and sell
|
|
448
|
+
- Commercial use allowed, with no obligation to open-source your changes
|
|
449
|
+
- Keep the copyright notice; that's the whole obligation
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## ๐ Related Projects
|
|
454
|
+
|
|
455
|
+
**Commune Ecosystem**:
|
|
456
|
+
- **Devon's Homelab** - Personal infrastructure (private, showcase only)
|
|
457
|
+
|
|
458
|
+
**Inspired by**:
|
|
459
|
+
- [Andy Matuschak's Notes](https://notes.andymatuschak.org/)
|
|
460
|
+
- [Maggie Appleton's Digital Garden](https://maggieappleton.com/garden)
|
|
461
|
+
- [Obsidian](https://obsidian.md/) (proprietary alternative)
|
|
462
|
+
- [Logseq](https://logseq.com/) (local-first alternative)
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
**Created by**: [Devon Meadows](https://devonmeadows.com)
|
|
467
|
+
**Repository**: [dmthepm/commune-wiki](https://github.com/dmthepm/commune-wiki)
|
|
468
|
+
**Support**: [GitHub Issues](https://github.com/dmthepm/commune-wiki/issues)
|
package/bin/commune.mjs
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The `commune` entry point.
|
|
5
|
+
*
|
|
6
|
+
* One line on purpose, and this is the line #18 promised would change: it now
|
|
7
|
+
* imports the compiled CLI rather than the TypeScript source. Node refuses to
|
|
8
|
+
* strip types from any file under a `node_modules` path, so a `.ts` import here
|
|
9
|
+
* works from a checkout and dies the moment this package is installed.
|
|
10
|
+
*
|
|
11
|
+
* Two things have to be true for the import below to resolve on an install,
|
|
12
|
+
* and both are in `package.json`. `prepare` runs `tsc`, which pnpm executes
|
|
13
|
+
* inside its own clone of a git dependency, so `lib/` gets *built*. And the
|
|
14
|
+
* `files` allowlist names `lib`, so `lib/` gets *packed* โ without that field
|
|
15
|
+
* the pack falls back to `.gitignore`, which ignores build output, and the
|
|
16
|
+
* installed package would arrive with a bin and nothing for it to run.
|
|
17
|
+
* `tests/install.test.mjs` and the CI stranger-install step exist to catch
|
|
18
|
+
* exactly that, because a checkout never notices it.
|
|
19
|
+
*
|
|
20
|
+
* The package compiles to `lib/`, not `dist/`: `dist/` is Astro's, and
|
|
21
|
+
* `astro build` empties its output directory before every run, so a site build
|
|
22
|
+
* would delete the CLI that is about to check it.
|
|
23
|
+
*
|
|
24
|
+
* If `lib/` is missing in a checkout, `pnpm build:lib` writes it.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { run } from '../lib/cli/main.js';
|
|
28
|
+
|
|
29
|
+
process.exitCode = await run(process.argv.slice(2));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `commune check` โ link integrity as a payload.
|
|
3
|
+
*
|
|
4
|
+
* Exits 0 whether or not it finds anything. The exit code answers "did the
|
|
5
|
+
* command finish", not "is your content clean" โ those are different questions
|
|
6
|
+
* and an agent that cannot tell them apart has to parse stderr to find out
|
|
7
|
+
* whether the tool crashed. `commune gate` is the documented exception โ it keeps
|
|
8
|
+
* its exit 1, because a gate's job *is* to fail.
|
|
9
|
+
*
|
|
10
|
+
* v1 is scoped to link integrity so it does not block on #17's collection
|
|
11
|
+
* collapse. Frontmatter drift is a follow-up.
|
|
12
|
+
*/
|
|
13
|
+
export declare function checkCommand(root: string, json: boolean): Promise<number>;
|
package/lib/cli/check.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `commune check` โ link integrity as a payload.
|
|
3
|
+
*
|
|
4
|
+
* Exits 0 whether or not it finds anything. The exit code answers "did the
|
|
5
|
+
* command finish", not "is your content clean" โ those are different questions
|
|
6
|
+
* and an agent that cannot tell them apart has to parse stderr to find out
|
|
7
|
+
* whether the tool crashed. `commune gate` is the documented exception โ it keeps
|
|
8
|
+
* its exit 1, because a gate's job *is* to fail.
|
|
9
|
+
*
|
|
10
|
+
* v1 is scoped to link integrity so it does not block on #17's collection
|
|
11
|
+
* collapse. Frontmatter drift is a follow-up.
|
|
12
|
+
*/
|
|
13
|
+
import { buildGraph, checkEntries, loadContentEntries, } from "../lib/graph.js";
|
|
14
|
+
import { SCHEMA, writeJson, writeLines } from "./render.js";
|
|
15
|
+
import { EXIT_OK } from "./errors.js";
|
|
16
|
+
const RULES = [
|
|
17
|
+
'broken-link',
|
|
18
|
+
'ambiguous-target',
|
|
19
|
+
'duplicate-name',
|
|
20
|
+
'noncanonical-title',
|
|
21
|
+
];
|
|
22
|
+
/** A finding as it appears in the payload: no internal rendering fields. */
|
|
23
|
+
function toFinding(diagnostic) {
|
|
24
|
+
return {
|
|
25
|
+
rule: diagnostic.rule,
|
|
26
|
+
severity: diagnostic.severity,
|
|
27
|
+
file: diagnostic.file,
|
|
28
|
+
...(diagnostic.line !== undefined ? { line: diagnostic.line } : {}),
|
|
29
|
+
message: diagnostic.message,
|
|
30
|
+
...(diagnostic.target !== undefined ? { target: diagnostic.target } : {}),
|
|
31
|
+
...(diagnostic.candidates ? { candidates: diagnostic.candidates } : {}),
|
|
32
|
+
...(diagnostic.canonical !== undefined ? { canonical: diagnostic.canonical } : {}),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export async function checkCommand(root, json) {
|
|
36
|
+
const entries = await loadContentEntries({ root });
|
|
37
|
+
const graph = buildGraph(entries);
|
|
38
|
+
const findings = checkEntries(entries, graph);
|
|
39
|
+
const byRule = Object.fromEntries(RULES.map((rule) => [rule, findings.filter((finding) => finding.rule === rule).length]));
|
|
40
|
+
const summary = {
|
|
41
|
+
entries: Object.keys(graph.nodes).length,
|
|
42
|
+
// Resolved edges. Every resolved outbound link is one inbound link on the
|
|
43
|
+
// far side, so this is the same number counted from either end.
|
|
44
|
+
edges: graph.totalBacklinks,
|
|
45
|
+
errors: findings.filter((finding) => finding.severity === 'error').length,
|
|
46
|
+
warnings: findings.filter((finding) => finding.severity === 'warning').length,
|
|
47
|
+
byRule,
|
|
48
|
+
};
|
|
49
|
+
if (json) {
|
|
50
|
+
writeJson({ schema: SCHEMA, root, summary, findings: findings.map(toFinding) });
|
|
51
|
+
return EXIT_OK;
|
|
52
|
+
}
|
|
53
|
+
writeLines([
|
|
54
|
+
...findings.map((finding) => `${finding.severity}\t${finding.rule}\t${finding.file}\t${finding.message}`),
|
|
55
|
+
`${summary.entries} entries, ${summary.edges} edges, ${summary.errors} errors, ${summary.warnings} warnings`,
|
|
56
|
+
]);
|
|
57
|
+
return EXIT_OK;
|
|
58
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exit codes and the errors that produce them.
|
|
3
|
+
*
|
|
4
|
+
* The codes report *completion*, not findings: a `check` that reports 28 broken
|
|
5
|
+
* links has finished its job and exits 0. An agent that cannot tell "your
|
|
6
|
+
* content has problems" from "the tool fell over" has to parse stderr to find
|
|
7
|
+
* out, which is the failure mode this contract exists to prevent.
|
|
8
|
+
*/
|
|
9
|
+
/** The command ran to completion. Findings, if any, are in the payload. */
|
|
10
|
+
export declare const EXIT_OK = 0;
|
|
11
|
+
/** The command could not finish: bad root, unreadable file, unparseable frontmatter. */
|
|
12
|
+
export declare const EXIT_FAILED = 1;
|
|
13
|
+
/** The command was invoked wrongly: unknown flag, missing value, unknown subcommand. */
|
|
14
|
+
export declare const EXIT_USAGE = 2;
|
|
15
|
+
export type ErrorCode = 'EUSAGE' | 'ENOCONTENT' | 'EPARSE' | 'EINTERNAL';
|
|
16
|
+
/** An error the CLI knows how to render on either side of the `--json` switch. */
|
|
17
|
+
export declare class CliError extends Error {
|
|
18
|
+
readonly code: ErrorCode;
|
|
19
|
+
readonly exitCode: number;
|
|
20
|
+
/** Extra lines for text mode only โ usage, for instance. Never in the JSON object. */
|
|
21
|
+
readonly detail?: string;
|
|
22
|
+
constructor(code: ErrorCode, message: string, exitCode: number, detail?: string);
|
|
23
|
+
}
|
|
24
|
+
export declare function usageError(message: string, detail?: string): CliError;
|
|
25
|
+
export declare function failure(code: ErrorCode, message: string): CliError;
|
|
26
|
+
/** `parseArgs` throws typed errors; every one of them means the invocation was wrong. */
|
|
27
|
+
export declare function isParseArgsError(error: unknown): error is Error & {
|
|
28
|
+
code: string;
|
|
29
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exit codes and the errors that produce them.
|
|
3
|
+
*
|
|
4
|
+
* The codes report *completion*, not findings: a `check` that reports 28 broken
|
|
5
|
+
* links has finished its job and exits 0. An agent that cannot tell "your
|
|
6
|
+
* content has problems" from "the tool fell over" has to parse stderr to find
|
|
7
|
+
* out, which is the failure mode this contract exists to prevent.
|
|
8
|
+
*/
|
|
9
|
+
/** The command ran to completion. Findings, if any, are in the payload. */
|
|
10
|
+
export const EXIT_OK = 0;
|
|
11
|
+
/** The command could not finish: bad root, unreadable file, unparseable frontmatter. */
|
|
12
|
+
export const EXIT_FAILED = 1;
|
|
13
|
+
/** The command was invoked wrongly: unknown flag, missing value, unknown subcommand. */
|
|
14
|
+
export const EXIT_USAGE = 2;
|
|
15
|
+
/** An error the CLI knows how to render on either side of the `--json` switch. */
|
|
16
|
+
export class CliError extends Error {
|
|
17
|
+
code;
|
|
18
|
+
exitCode;
|
|
19
|
+
/** Extra lines for text mode only โ usage, for instance. Never in the JSON object. */
|
|
20
|
+
detail;
|
|
21
|
+
constructor(code, message, exitCode, detail) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = 'CliError';
|
|
24
|
+
this.code = code;
|
|
25
|
+
this.exitCode = exitCode;
|
|
26
|
+
this.detail = detail;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function usageError(message, detail) {
|
|
30
|
+
return new CliError('EUSAGE', message, EXIT_USAGE, detail);
|
|
31
|
+
}
|
|
32
|
+
export function failure(code, message) {
|
|
33
|
+
return new CliError(code, message, EXIT_FAILED);
|
|
34
|
+
}
|
|
35
|
+
/** `parseArgs` throws typed errors; every one of them means the invocation was wrong. */
|
|
36
|
+
export function isParseArgsError(error) {
|
|
37
|
+
if (!(error instanceof Error))
|
|
38
|
+
return false;
|
|
39
|
+
const code = error.code;
|
|
40
|
+
return typeof code === 'string' && code.startsWith('ERR_PARSE_ARGS_');
|
|
41
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `commune gate` โ the build check, as a verb.
|
|
3
|
+
*
|
|
4
|
+
* This is the one command in the CLI whose exit code answers a question about
|
|
5
|
+
* your *content* rather than about the command. Everywhere else the contract is
|
|
6
|
+
* "0 means I finished, findings or not", precisely so an agent can tell a dirty
|
|
7
|
+
* vault from a broken tool. A gate inverts that on purpose: its whole job is to
|
|
8
|
+
* stop a build, and a build stops on a non-zero exit. `usage.ts` says so out
|
|
9
|
+
* loud, because a reader who has internalised the rule needs to be told where
|
|
10
|
+
* the exception is.
|
|
11
|
+
*
|
|
12
|
+
* It was `scripts/test-search-index.mjs`, which imported the graph core by
|
|
13
|
+
* relative path. That works from a checkout and is unreachable from
|
|
14
|
+
* `node_modules`, so the one repo that most needs this check โ a wiki built
|
|
15
|
+
* with the package โ was the one repo that could not run it. The three
|
|
16
|
+
* assertions are unchanged; only the way you invoke them is.
|
|
17
|
+
*
|
|
18
|
+
* Three assertions:
|
|
19
|
+
* 1. every page in the `pages` collection is present in the search index
|
|
20
|
+
* 2. every WikiLink that resolves uses the target's exact title (no pipes,
|
|
21
|
+
* no case drift) โ the canonical-title rule
|
|
22
|
+
* 3. WikiLinks pointing at standalone pages actually render as hrefs
|
|
23
|
+
*
|
|
24
|
+
* The canonical-title rule itself lives in the graph core, where `commune
|
|
25
|
+
* check` reports it as a `noncanonical-title` finding. This verb is the *gate*:
|
|
26
|
+
* same rule, same findings, but a build that violates it stops. Two copies of
|
|
27
|
+
* one rule is the bug #3 was opened to kill, so there is only ever one.
|
|
28
|
+
*/
|
|
29
|
+
/** Which assertion failed, and what it saw. */
|
|
30
|
+
export interface GateFailure {
|
|
31
|
+
assertion: 'pages-indexed' | 'canonical-titles' | 'page-links-rendered';
|
|
32
|
+
message: string;
|
|
33
|
+
}
|
|
34
|
+
export declare function gateCommand(root: string, dist: string, json: boolean): Promise<number>;
|