@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
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The content graph core.
|
|
3
|
+
*
|
|
4
|
+
* Every mechanism that needs to know "what content exists and where does it
|
|
5
|
+
* live" reads from here: the remark WikiLinks plugin, the backlinks Astro
|
|
6
|
+
* integration, and the search-index check. Before this module those three
|
|
7
|
+
* carried hand-synced copies of the same directory scan, the same visibility
|
|
8
|
+
* rules, and three subtly different opinions about trailing slashes.
|
|
9
|
+
*
|
|
10
|
+
* The rules this module owns:
|
|
11
|
+
* - which collections participate (notes, research, pages)
|
|
12
|
+
* - visibility (notes opt in with `visibility: public`; research and pages
|
|
13
|
+
* are always public)
|
|
14
|
+
* - canonical URLs, always with a trailing slash, matching Astro's
|
|
15
|
+
* directory build format
|
|
16
|
+
* - the title/alias lookup used to resolve `[[WikiLinks]]`
|
|
17
|
+
* - which link forms count as an edge, and how each one resolves
|
|
18
|
+
*/
|
|
19
|
+
export type CollectionName = 'notes' | 'research' | 'pages';
|
|
20
|
+
/** Where each collection's markdown lives, relative to the project root. */
|
|
21
|
+
export declare const CONTENT_DIRS: Record<CollectionName, string>;
|
|
22
|
+
/** Collection scan order. Stable so derived artifacts are deterministic. */
|
|
23
|
+
export declare const COLLECTIONS: CollectionName[];
|
|
24
|
+
/** One piece of public content, as the graph sees it. */
|
|
25
|
+
export interface ContentEntry {
|
|
26
|
+
/** Collection-relative slug, e.g. `evergreen-notes`. Not unique across collections. */
|
|
27
|
+
slug: string;
|
|
28
|
+
/** Canonical site path with a trailing slash, e.g. `/notes/evergreen-notes/`. Unique. */
|
|
29
|
+
urlPath: string;
|
|
30
|
+
title: string;
|
|
31
|
+
collection: CollectionName;
|
|
32
|
+
aliases: string[];
|
|
33
|
+
tags: string[];
|
|
34
|
+
status: string;
|
|
35
|
+
summary?: string;
|
|
36
|
+
updated?: string;
|
|
37
|
+
/** Markdown body with frontmatter stripped. */
|
|
38
|
+
body: string;
|
|
39
|
+
/** Parsed frontmatter. Kept because links can live in it. */
|
|
40
|
+
frontmatter: Record<string, unknown>;
|
|
41
|
+
/** Source file path, relative to the project root. */
|
|
42
|
+
file: string;
|
|
43
|
+
}
|
|
44
|
+
/** What a resolved `[[WikiLink]]` points at. */
|
|
45
|
+
export interface LinkTarget {
|
|
46
|
+
slug: string;
|
|
47
|
+
collection: CollectionName;
|
|
48
|
+
urlPath: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Convert a content file path to its collection-relative slug and canonical URL.
|
|
52
|
+
*
|
|
53
|
+
* Files are named for their titles, so that `[[Evergreen Notes]]` resolves in
|
|
54
|
+
* Obsidian (which matches on filename) and in Astro (which matches on title)
|
|
55
|
+
* without a piped alias. The URL is therefore *derived* from the filename, not
|
|
56
|
+
* equal to it, using the same `github-slugger` call Astro's own content layer
|
|
57
|
+
* uses — so the graph and Astro's `entry.slug` can never disagree.
|
|
58
|
+
*
|
|
59
|
+
* Two escape hatches, in priority order:
|
|
60
|
+
* - `slug` frontmatter pins the URL when a rename would otherwise move it.
|
|
61
|
+
* Astro treats this key as reserved and strips it before Zod validation, so
|
|
62
|
+
* it must not appear in any collection schema.
|
|
63
|
+
* - `pages` declare a whole `url`, since they render at arbitrary routes.
|
|
64
|
+
*/
|
|
65
|
+
export declare function toUrlPath(file: string, collection: CollectionName, data?: Record<string, unknown>): {
|
|
66
|
+
slug: string;
|
|
67
|
+
urlPath: string;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Convert a canonical URL path to the *file* path of its markdown twin.
|
|
71
|
+
*
|
|
72
|
+
* Every note is served twice at one URL: `/notes/cake/` renders the page,
|
|
73
|
+
* `/notes/cake.md` returns the source file. The mapping is pure string work —
|
|
74
|
+
* drop the trailing slash, append `.md` — and it lives here rather than in the
|
|
75
|
+
* build writer so the page templates cannot drift from the files that writer
|
|
76
|
+
* actually emits. The home page (`/`) has no name to hang the suffix on, so it
|
|
77
|
+
* gets `index.md`.
|
|
78
|
+
*
|
|
79
|
+
* The result is relative, with no leading slash: the caller joins it onto its
|
|
80
|
+
* output directory. For the URL a rendered page should link to, use
|
|
81
|
+
* `toMarkdownHref`. Traversal segments are rejected rather than normalized,
|
|
82
|
+
* since a `url` frontmatter is author-controlled and a `..` in it would
|
|
83
|
+
* otherwise let a build write outside its output directory.
|
|
84
|
+
*/
|
|
85
|
+
export declare function toMarkdownPath(urlPath: string): string;
|
|
86
|
+
/**
|
|
87
|
+
* The site-absolute URL of an entry's markdown twin — what a rendered page links to.
|
|
88
|
+
*
|
|
89
|
+
* Derived from `toMarkdownPath` rather than computed alongside it, so a page's
|
|
90
|
+
* "view as markdown" link and the file the build writes can only ever be the
|
|
91
|
+
* same string with a leading slash.
|
|
92
|
+
*/
|
|
93
|
+
export declare function toMarkdownHref(urlPath: string): string;
|
|
94
|
+
/** Coerce a frontmatter date to `yyyy-mm-dd`, so build output has no timestamp drift. */
|
|
95
|
+
export declare function normalizeDate(value: unknown): string | undefined;
|
|
96
|
+
/** Where to read content from. */
|
|
97
|
+
export interface GraphOptions {
|
|
98
|
+
/**
|
|
99
|
+
* The project root: the directory that *contains* `src/content`, not
|
|
100
|
+
* `src/content` itself.
|
|
101
|
+
*
|
|
102
|
+
* Every path the graph exposes is derived from this directory — `file` is
|
|
103
|
+
* relative to it, and `toUrlPath` strips `CONTENT_DIRS[collection]` off the
|
|
104
|
+
* front of `file` to get a slug. Point it one level too deep and every slug
|
|
105
|
+
* silently changes. Defaults to `process.cwd()`.
|
|
106
|
+
*/
|
|
107
|
+
root?: string;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Read every public content entry, in a stable order.
|
|
111
|
+
*
|
|
112
|
+
* Deliberately uncached: the backlinks integration runs this on each build hook
|
|
113
|
+
* and the dev server must see content edits without a restart.
|
|
114
|
+
*
|
|
115
|
+
* The root is a parameter rather than a `process.chdir`, so one process can
|
|
116
|
+
* read several vaults — which is what the CLI does when it is pointed at
|
|
117
|
+
* another wiki — without the cwd becoming shared mutable state.
|
|
118
|
+
*/
|
|
119
|
+
export declare function loadContentEntries(options?: GraphOptions): Promise<ContentEntry[]>;
|
|
120
|
+
/**
|
|
121
|
+
* Build the `[[WikiLink]]` resolution table: lowercased title or alias → target.
|
|
122
|
+
*
|
|
123
|
+
* Titles win over aliases, and within each pass later entries win — which only
|
|
124
|
+
* matters when two pieces of content claim the same name, and is a content bug
|
|
125
|
+
* either way.
|
|
126
|
+
*/
|
|
127
|
+
export declare function buildLinkLookup(entries: ContentEntry[]): Map<string, LinkTarget>;
|
|
128
|
+
/**
|
|
129
|
+
* Build the url resolution table: canonical `urlPath` → target.
|
|
130
|
+
*
|
|
131
|
+
* Separate from the title/alias lookup on purpose. A path and a title are
|
|
132
|
+
* different namespaces, and mixing them is what let `/notes/atomic-notes`
|
|
133
|
+
* resolve through an alias rather than through the route it actually names.
|
|
134
|
+
*/
|
|
135
|
+
export declare function buildUrlLookup(entries: ContentEntry[]): Map<string, LinkTarget>;
|
|
136
|
+
/**
|
|
137
|
+
* Build the filename resolution table: lowercased basename → target.
|
|
138
|
+
*
|
|
139
|
+
* Not a resolution table — nothing resolves through it. It exists so `check`
|
|
140
|
+
* can notice when a name means two different things depending on which table
|
|
141
|
+
* you ask: `[[Index]]` resolves by *title*, but `[index](./Index.md)` names a
|
|
142
|
+
* *file*, and in a vault where those disagree one of the two is silently wrong.
|
|
143
|
+
* Obsidian resolves by filename and Astro by title, so this is the seam where
|
|
144
|
+
* the two tools stop agreeing about what a link points at.
|
|
145
|
+
*/
|
|
146
|
+
export declare function buildBasenameLookup(entries: ContentEntry[]): Map<string, LinkTarget[]>;
|
|
147
|
+
/**
|
|
148
|
+
* Resolve one extracted edge, using the table its kind belongs to.
|
|
149
|
+
*
|
|
150
|
+
* Deliberately does nothing clever: no basename fallback, no source-relative
|
|
151
|
+
* disambiguation, no collision policy. Those are resolution questions, tracked
|
|
152
|
+
* on #18; this is extraction's other half and nothing more.
|
|
153
|
+
*/
|
|
154
|
+
export declare function resolveLink(link: ExtractedLink, byName: Map<string, LinkTarget>, byUrl: Map<string, LinkTarget>): LinkTarget | undefined;
|
|
155
|
+
/**
|
|
156
|
+
* The link lookup for one project root, built once and reused.
|
|
157
|
+
*
|
|
158
|
+
* The remark plugin runs per markdown file, so rescanning the content tree on
|
|
159
|
+
* every transform would make builds quadratic. Call `resetGraphCache()` if a
|
|
160
|
+
* caller needs to see content written during the same process.
|
|
161
|
+
*/
|
|
162
|
+
export declare function getLinkLookup(options?: GraphOptions): Promise<Map<string, LinkTarget>>;
|
|
163
|
+
export declare function resetGraphCache(): void;
|
|
164
|
+
/**
|
|
165
|
+
* Blank out fenced blocks and inline code, preserving offsets.
|
|
166
|
+
*
|
|
167
|
+
* A `[[WikiLink]]` written inside backticks is documentation *about* the
|
|
168
|
+
* syntax, not a link. The remark plugin gets this right for free because it
|
|
169
|
+
* only visits `text` nodes and never `inlineCode` — anything scanning raw
|
|
170
|
+
* markdown has to strip code itself or it reports phantom broken links.
|
|
171
|
+
*/
|
|
172
|
+
export declare function stripCode(content: string): string;
|
|
173
|
+
/**
|
|
174
|
+
* How a link target should be resolved.
|
|
175
|
+
*
|
|
176
|
+
* `name` is title-shaped: the raw text of a `[[WikiLink]]`, or the basename of
|
|
177
|
+
* a relative file link, resolved against the title/alias lookup.
|
|
178
|
+
* `url` is path-shaped: an absolute site path, resolved against `urlPath`.
|
|
179
|
+
*
|
|
180
|
+
* The two cannot be collapsed into one string. A bare `atomic-notes` is not a
|
|
181
|
+
* title and matching it against one only works by coincidence; `/notes/atomic-notes/`
|
|
182
|
+
* is not a title either and must never be looked up as one.
|
|
183
|
+
*/
|
|
184
|
+
export type LinkKind = 'name' | 'url';
|
|
185
|
+
/** One outbound edge, with enough information to resolve it. */
|
|
186
|
+
export interface ExtractedLink {
|
|
187
|
+
kind: LinkKind;
|
|
188
|
+
target: string;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Extract every outbound link target from a markdown body and its frontmatter.
|
|
192
|
+
*
|
|
193
|
+
* Returns the edges Obsidian's `resolvedLinks` would record: wikilinks, embeds,
|
|
194
|
+
* internal markdown links, and `frontmatterLinks`. Code is excluded.
|
|
195
|
+
*
|
|
196
|
+
* Frontmatter is passed separately because it is already parsed by the time it
|
|
197
|
+
* reaches here — `loadContentEntries()` splits it off with gray-matter, so the
|
|
198
|
+
* body alone can never contain it.
|
|
199
|
+
*/
|
|
200
|
+
export declare function extractLinks(content: string, frontmatter?: Record<string, unknown>): ExtractedLink[];
|
|
201
|
+
/**
|
|
202
|
+
* The shape of the star calculation strategy.
|
|
203
|
+
*
|
|
204
|
+
* Declared as an interface rather than inferred from the value, because the
|
|
205
|
+
* value picks one member of each union and `calculateStars` switches over all
|
|
206
|
+
* of them. With `as const` on the fields, TypeScript narrows `strategy` to the
|
|
207
|
+
* literal `'top-percent'` and every other arm of that switch becomes an error
|
|
208
|
+
* — the config would be untunable without editing the code that reads it,
|
|
209
|
+
* which is the opposite of what a config is for.
|
|
210
|
+
*/
|
|
211
|
+
export interface StarConfig {
|
|
212
|
+
strategy: 'top-percent' | 'top-absolute' | 'threshold';
|
|
213
|
+
/** For 'top-percent': what percentage gets stars. */
|
|
214
|
+
topPercent: number;
|
|
215
|
+
/** For 'top-absolute': how many notes get stars. */
|
|
216
|
+
topAbsolute: number;
|
|
217
|
+
/** For 'threshold': minimum backlinks to get a star. */
|
|
218
|
+
threshold: number;
|
|
219
|
+
/** Below this many notes, nobody is starred. */
|
|
220
|
+
minNotesForStars: number;
|
|
221
|
+
rankBy: 'backlinks' | 'revisions' | 'cross-theme' | 'weighted';
|
|
222
|
+
/** For 'weighted' ranking (future). */
|
|
223
|
+
weights: {
|
|
224
|
+
backlinks: number;
|
|
225
|
+
revisions: number;
|
|
226
|
+
crossTheme: number;
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Star calculation strategy.
|
|
231
|
+
*
|
|
232
|
+
* Lives in the core rather than the Astro integration because `isStarred` is a
|
|
233
|
+
* field of the public artifact, and the CLI has to be able to produce the same
|
|
234
|
+
* artifact without loading Astro.
|
|
235
|
+
*/
|
|
236
|
+
export declare const STAR_CONFIG: StarConfig;
|
|
237
|
+
/**
|
|
238
|
+
* One node of the public graph, and one entry of `backlinks.json`.
|
|
239
|
+
*
|
|
240
|
+
* The property order here is a serialization contract: `backlinks.json` is
|
|
241
|
+
* committed, and a reordered object is a diff on every build.
|
|
242
|
+
*/
|
|
243
|
+
export interface NoteMetadata {
|
|
244
|
+
/** Canonical URL path. The graph is keyed by URL, so this is the identity. */
|
|
245
|
+
slug: string;
|
|
246
|
+
title: string;
|
|
247
|
+
collection: CollectionName;
|
|
248
|
+
aliases: string[];
|
|
249
|
+
outbound: string[];
|
|
250
|
+
inbound: string[];
|
|
251
|
+
tags: string[];
|
|
252
|
+
status: string;
|
|
253
|
+
summary?: string;
|
|
254
|
+
updated?: string;
|
|
255
|
+
isStarred?: boolean;
|
|
256
|
+
}
|
|
257
|
+
/** Which rules the graph and `check` can report. */
|
|
258
|
+
export type DiagnosticRule = 'broken-link' | 'ambiguous-target' | 'duplicate-name' | 'noncanonical-title';
|
|
259
|
+
/**
|
|
260
|
+
* One finding, as data.
|
|
261
|
+
*
|
|
262
|
+
* The Astro integration used to print broken links inline, which meant the only
|
|
263
|
+
* way to get at them was to scrape build logs. Findings are values now, so the
|
|
264
|
+
* integration, `check` and the search-index gate can each render the same set
|
|
265
|
+
* their own way.
|
|
266
|
+
*/
|
|
267
|
+
export interface Diagnostic {
|
|
268
|
+
rule: DiagnosticRule;
|
|
269
|
+
severity: 'error' | 'warning';
|
|
270
|
+
/** Source file, relative to the project root. */
|
|
271
|
+
file: string;
|
|
272
|
+
/** Not tracked yet: `extractLinks` records no offsets. Added when #24 needs them. */
|
|
273
|
+
line?: number;
|
|
274
|
+
message: string;
|
|
275
|
+
/** The link text or name involved. */
|
|
276
|
+
target?: string;
|
|
277
|
+
/** Canonical URL of the entry the finding is in. Carried so build output can name the route. */
|
|
278
|
+
urlPath?: string;
|
|
279
|
+
/** How the offending link was spelled, which decides `[[name]]` vs `(url)` rendering. */
|
|
280
|
+
kind?: LinkKind;
|
|
281
|
+
/** urlPaths in contention, for `ambiguous-target` and `duplicate-name`. */
|
|
282
|
+
candidates?: string[];
|
|
283
|
+
/** The spelling a `noncanonical-title` finding should have used. */
|
|
284
|
+
canonical?: string;
|
|
285
|
+
}
|
|
286
|
+
/** The resolved content graph. */
|
|
287
|
+
export interface Graph {
|
|
288
|
+
/** Keyed by urlPath; insertion order is entry order. */
|
|
289
|
+
nodes: Record<string, NoteMetadata>;
|
|
290
|
+
diagnostics: Diagnostic[];
|
|
291
|
+
totalBacklinks: number;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Calculate which notes get stars based on STAR_CONFIG.
|
|
295
|
+
* Returns a Set of slugs that should be starred.
|
|
296
|
+
*/
|
|
297
|
+
export declare function calculateStars(notes: Map<string, NoteMetadata>): Set<string>;
|
|
298
|
+
/**
|
|
299
|
+
* Build the resolved graph from loaded entries.
|
|
300
|
+
*
|
|
301
|
+
* Pure: no filesystem, no logging, no Astro. Everything a caller might want to
|
|
302
|
+
* print comes back in `diagnostics`, so the same construction serves the build
|
|
303
|
+
* (which warns), `check` (which reports) and `related` (which does neither).
|
|
304
|
+
*/
|
|
305
|
+
export declare function buildGraph(entries: ContentEntry[]): Graph;
|
|
306
|
+
/**
|
|
307
|
+
* Render a diagnostic as the build has always rendered it.
|
|
308
|
+
*
|
|
309
|
+
* `public/backlinks.json` is not the only committed contract — build output is
|
|
310
|
+
* read by humans and diffed by reviewers, so the warning line is kept exactly
|
|
311
|
+
* as it was when the integration formatted it itself.
|
|
312
|
+
*/
|
|
313
|
+
export declare function formatDiagnostic(diagnostic: Diagnostic): string;
|
|
314
|
+
/**
|
|
315
|
+
* The public artifact, exactly as `public/backlinks.json` stores it.
|
|
316
|
+
*
|
|
317
|
+
* Separate from `Graph` because the file is a runtime contract with four
|
|
318
|
+
* client-side readers: the graph can grow fields, the file cannot.
|
|
319
|
+
*/
|
|
320
|
+
export declare function toBacklinksJson(graph: Graph): Record<string, NoteMetadata>;
|
|
321
|
+
/**
|
|
322
|
+
* Names claimed by more than one entry.
|
|
323
|
+
*
|
|
324
|
+
* Two triggers, because a name lives in two namespaces: a title or alias that
|
|
325
|
+
* two entries both answer to, and a filename that two entries share. Neither
|
|
326
|
+
* is an error the graph can resolve — `buildLinkLookup` keeps last-wins, which
|
|
327
|
+
* is what it has always done — but both mean a `[[WikiLink]]` reaches somewhere
|
|
328
|
+
* its author did not choose, and silently.
|
|
329
|
+
*
|
|
330
|
+
* Reported against the *first* claimant, because that is the entry the
|
|
331
|
+
* collision makes unreachable.
|
|
332
|
+
*/
|
|
333
|
+
export declare function findDuplicateNames(entries: ContentEntry[]): Diagnostic[];
|
|
334
|
+
/**
|
|
335
|
+
* WikiLinks that resolve but do not name their target exactly.
|
|
336
|
+
*
|
|
337
|
+
* The rule this implements used to live in the build gate script, with its own
|
|
338
|
+
* lookup table and its own regex — a second copy of a rule the graph core
|
|
339
|
+
* already had the ingredients for, which is the bug class #3 was opened to
|
|
340
|
+
* kill. One implementation now, two callers: `check` reports it and
|
|
341
|
+
* `commune gate` fails on it.
|
|
342
|
+
*
|
|
343
|
+
* Worth checking precisely because it is invisible: a piped link or a
|
|
344
|
+
* near-miss title still renders, and the vault quietly decouples from the site.
|
|
345
|
+
*/
|
|
346
|
+
export declare function findNoncanonicalTitles(entries: ContentEntry[]): Diagnostic[];
|
|
347
|
+
/**
|
|
348
|
+
* Every finding `check` reports.
|
|
349
|
+
*
|
|
350
|
+
* The graph's own diagnostics come first and in entry order, so the list reads
|
|
351
|
+
* the same way the build log does, then the two whole-corpus rules that need
|
|
352
|
+
* every entry in hand before they can fire.
|
|
353
|
+
*/
|
|
354
|
+
export declare function checkEntries(entries: ContentEntry[], graph: Graph): Diagnostic[];
|