diffwiki-core 0.17.3 → 0.18.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/dist/chunk-FJ5XD2JN.js +63 -0
- package/dist/{chunk-TXM4OGBA.js → chunk-KUX7GHHM.js} +315 -74
- package/dist/{chunk-6MW6KSS4.js → chunk-NLBHSMHA.js} +32 -2
- package/dist/events-UG4JPZDZ.js +7 -0
- package/dist/{frontmatter-ghMCpLNf.d.ts → frontmatter-D7gO1fg4.d.cts} +3 -1
- package/dist/{frontmatter-ghMCpLNf.d.cts → frontmatter-D7gO1fg4.d.ts} +3 -1
- package/dist/index.cjs +515 -179
- package/dist/index.d.cts +48 -5
- package/dist/index.d.ts +48 -5
- package/dist/index.js +75 -60
- package/dist/plugin.cjs +1 -1
- package/dist/plugin.d.cts +2 -2
- package/dist/plugin.d.ts +2 -2
- package/dist/plugin.js +2 -1
- package/package.json +1 -1
- package/dist/events-K3MKWACA.js +0 -6
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var DiffwikiError = class extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = new.target.name;
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
var CollectionExistsError = class extends DiffwikiError {
|
|
9
|
+
constructor(name) {
|
|
10
|
+
super(`collection "${name}" already exists`);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var CollectionNotFoundError = class extends DiffwikiError {
|
|
14
|
+
constructor(name) {
|
|
15
|
+
super(`collection "${name}" not found`);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var NoDefaultCollectionError = class extends DiffwikiError {
|
|
19
|
+
constructor() {
|
|
20
|
+
super("no default collection set (use: diffwiki config-set defaultCollection <name>)");
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var ArticleNotFoundError = class extends DiffwikiError {
|
|
24
|
+
constructor(target) {
|
|
25
|
+
super(`article "${target}" not found`);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var InvalidTargetError = class extends DiffwikiError {
|
|
29
|
+
constructor(target, expected) {
|
|
30
|
+
super(`invalid target "${target}" \u2014 ${expected}`);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var PluginError = class extends DiffwikiError {
|
|
34
|
+
};
|
|
35
|
+
var WorktreeNotAllowedError = class extends DiffwikiError {
|
|
36
|
+
constructor(root) {
|
|
37
|
+
super(`refusing to add a linked worktree \u2014 add the source repository instead (${root})`);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var CollectionAlreadyAddedError = class extends DiffwikiError {
|
|
41
|
+
constructor(name) {
|
|
42
|
+
super(`this repository is already added as collection "${name}"`);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var InvalidExportSelectionError = class extends DiffwikiError {
|
|
46
|
+
constructor(problems) {
|
|
47
|
+
super(`export aborted \u2014 ${problems.length} selection problem(s):
|
|
48
|
+
${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export {
|
|
53
|
+
DiffwikiError,
|
|
54
|
+
CollectionExistsError,
|
|
55
|
+
CollectionNotFoundError,
|
|
56
|
+
NoDefaultCollectionError,
|
|
57
|
+
ArticleNotFoundError,
|
|
58
|
+
InvalidTargetError,
|
|
59
|
+
PluginError,
|
|
60
|
+
WorktreeNotAllowedError,
|
|
61
|
+
CollectionAlreadyAddedError,
|
|
62
|
+
InvalidExportSelectionError
|
|
63
|
+
};
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CollectionExistsError,
|
|
3
|
+
CollectionNotFoundError,
|
|
4
|
+
DiffwikiError,
|
|
5
|
+
InvalidTargetError
|
|
6
|
+
} from "./chunk-FJ5XD2JN.js";
|
|
7
|
+
|
|
1
8
|
// src/collections.ts
|
|
2
|
-
import
|
|
9
|
+
import fs3 from "fs/promises";
|
|
3
10
|
|
|
4
11
|
// src/paths.ts
|
|
5
12
|
import os from "os";
|
|
@@ -20,6 +27,9 @@ function collectionsDir() {
|
|
|
20
27
|
function templatesDir() {
|
|
21
28
|
return path.join(resolveHome(), "templates");
|
|
22
29
|
}
|
|
30
|
+
function metaDir() {
|
|
31
|
+
return path.join(resolveHome(), "meta");
|
|
32
|
+
}
|
|
23
33
|
function collectionDir(name) {
|
|
24
34
|
return path.join(collectionsDir(), name);
|
|
25
35
|
}
|
|
@@ -31,13 +41,289 @@ function pluginsRegistryPath() {
|
|
|
31
41
|
}
|
|
32
42
|
|
|
33
43
|
// src/registry.ts
|
|
44
|
+
import fs2 from "fs/promises";
|
|
45
|
+
|
|
46
|
+
// src/meta.ts
|
|
34
47
|
import fs from "fs/promises";
|
|
48
|
+
import path2 from "path";
|
|
49
|
+
import { randomUUID } from "crypto";
|
|
50
|
+
import { stringify, parseDocument } from "yaml";
|
|
51
|
+
var META_KINDS = ["history", "external-source-extract", "dream-learning"];
|
|
52
|
+
function validateMetaArticlePath(file) {
|
|
53
|
+
if (!/\.mdx?$/i.test(file) || path2.isAbsolute(file) || /[\\\0]/.test(file) || file.split("/").some((part) => !part || part === "." || part === ".." || part.startsWith("."))) {
|
|
54
|
+
throw new InvalidTargetError(file, "expected an exact relative .md/.mdx file path without traversal");
|
|
55
|
+
}
|
|
56
|
+
return file;
|
|
57
|
+
}
|
|
58
|
+
async function ensureCollectionMeta(entry) {
|
|
59
|
+
const root = collectionRoot(entry.name);
|
|
60
|
+
const source = path2.resolve(entry.path);
|
|
61
|
+
if (within(source, path2.resolve(metaDir())) || within(path2.resolve(metaDir()), source)) {
|
|
62
|
+
throw new InvalidTargetError(entry.path, "collection content overlaps the metadata store");
|
|
63
|
+
}
|
|
64
|
+
const index = path2.join(root, ".meta", "META.md");
|
|
65
|
+
await seed(index, `Sources for ${entry.name}`);
|
|
66
|
+
await globalIndex();
|
|
67
|
+
return index;
|
|
68
|
+
}
|
|
69
|
+
async function ensureArticleMeta(entry, articlePath) {
|
|
70
|
+
const relative = validateMetaArticlePath(articlePath);
|
|
71
|
+
await ensureCollectionMeta(entry);
|
|
72
|
+
const mirror = path2.join(collectionRoot(entry.name), relative);
|
|
73
|
+
if (await seed(mirror, `Sources for ${entry.name}:${relative}`)) await reindexMeta(entry.name);
|
|
74
|
+
return mirror;
|
|
75
|
+
}
|
|
76
|
+
async function mirrorSavedArticle(entry, file) {
|
|
77
|
+
try {
|
|
78
|
+
await ensureArticleMeta(entry, path2.relative(entry.path, file).split(path2.sep).join("/"));
|
|
79
|
+
} catch (err) {
|
|
80
|
+
throw new DiffwikiError(
|
|
81
|
+
`Article saved at ${file}, but metadata setup failed: ${String(err)}. Repair with: diffwiki meta init ${entry.name}`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function reindexMeta(collection) {
|
|
86
|
+
await fs.mkdir(await checked(metaDir()), { recursive: true });
|
|
87
|
+
const result = { created: [], kept: [], index_paths: [], warnings: [], errors: [] };
|
|
88
|
+
const names = collection ? [collection] : (await fs.readdir(metaDir(), { withFileTypes: true })).filter((item) => item.isDirectory() && !item.name.startsWith(".")).map((item) => item.name).sort();
|
|
89
|
+
for (const name of names) {
|
|
90
|
+
try {
|
|
91
|
+
const root = await checked(collectionRoot(name));
|
|
92
|
+
const index = path2.join(root, ".meta", "META.md");
|
|
93
|
+
await fs.access(await checked(index));
|
|
94
|
+
const pages = await markdownFiles(root);
|
|
95
|
+
const recordDir = await checked(path2.join(root, ".meta", "records"));
|
|
96
|
+
let records = [];
|
|
97
|
+
try {
|
|
98
|
+
records = (await fs.readdir(recordDir)).filter((file) => file.endsWith(".md")).sort();
|
|
99
|
+
} catch (err) {
|
|
100
|
+
if (!missing(err)) throw err;
|
|
101
|
+
}
|
|
102
|
+
const pageLines = new Map(pages.map((file) => [file, []]));
|
|
103
|
+
const sourceLines = [];
|
|
104
|
+
const dreamLines = [];
|
|
105
|
+
for (const record of records) {
|
|
106
|
+
const file = await checked(path2.join(recordDir, record));
|
|
107
|
+
try {
|
|
108
|
+
const raw = await fs.readFile(file, "utf8");
|
|
109
|
+
const front = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(raw);
|
|
110
|
+
if (!front) throw new Error("missing record header");
|
|
111
|
+
const doc = parseDocument(front[1]);
|
|
112
|
+
if (doc.errors.length) throw new Error("invalid YAML header");
|
|
113
|
+
const data = doc.toJS();
|
|
114
|
+
if (!data || !META_KINDS.includes(data.kind) || data.collection !== name || typeof data.captured_at !== "string") {
|
|
115
|
+
throw new Error("invalid kind, collection, or capture time");
|
|
116
|
+
}
|
|
117
|
+
const article = data.article_path === void 0 ? void 0 : validateMetaArticlePath(String(data.article_path));
|
|
118
|
+
const label = `${data.kind} \xB7 ${record.replace(/\.md$/, "")}`;
|
|
119
|
+
const bucket = data.kind === "dream-learning" ? dreamLines : sourceLines;
|
|
120
|
+
bucket.push(`- ${link(index, file, label)}${article ? ` \u2014 \`${article}\`` : " \u2014 collection"}`);
|
|
121
|
+
if (article) {
|
|
122
|
+
if (!pageLines.has(article))
|
|
123
|
+
result.warnings.push(`${file}: no manifest for ${article}; run meta init if the article exists`);
|
|
124
|
+
else pageLines.get(article).push(`- ${link(path2.join(root, article), file, label)}`);
|
|
125
|
+
}
|
|
126
|
+
} catch (err) {
|
|
127
|
+
result.warnings.push(`${file}: ${String(err)}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
for (const [page, lines] of pageLines) {
|
|
131
|
+
const file = path2.join(root, page);
|
|
132
|
+
await listing(file, `Sources for ${name}:${page}`, [
|
|
133
|
+
`Collection: ${link(file, index, "collection sources")}`,
|
|
134
|
+
"",
|
|
135
|
+
...lines.length ? lines : ["No page source history captured."]
|
|
136
|
+
]);
|
|
137
|
+
}
|
|
138
|
+
await listing(index, `Sources for ${name}`, [
|
|
139
|
+
"## Raw sources",
|
|
140
|
+
"",
|
|
141
|
+
...sourceLines.length ? sourceLines : ["No raw sources captured."],
|
|
142
|
+
"",
|
|
143
|
+
"## Dream learnings",
|
|
144
|
+
"",
|
|
145
|
+
...dreamLines.length ? dreamLines : ["No dream learnings captured."],
|
|
146
|
+
"",
|
|
147
|
+
"## Pages",
|
|
148
|
+
"",
|
|
149
|
+
...pages.map((page) => `- ${link(index, path2.join(root, page), page)}`)
|
|
150
|
+
]);
|
|
151
|
+
result.index_paths.push(index);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
result.errors.push(`${name}: ${String(err)}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
result.index_paths.push(await globalIndex());
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
async function initMeta(entries) {
|
|
160
|
+
const result = { created: [], kept: [], index_paths: [], warnings: [], errors: [] };
|
|
161
|
+
for (const entry of entries) {
|
|
162
|
+
try {
|
|
163
|
+
const files = await markdownFiles(entry.path);
|
|
164
|
+
await ensureCollectionMeta(entry);
|
|
165
|
+
for (const file of files) {
|
|
166
|
+
const mirror = path2.join(collectionRoot(entry.name), validateMetaArticlePath(file));
|
|
167
|
+
(await seed(mirror, `Sources for ${entry.name}:${file}`) ? result.created : result.kept).push(mirror);
|
|
168
|
+
}
|
|
169
|
+
const indexed = await reindexMeta(entry.name);
|
|
170
|
+
result.index_paths.push(...indexed.index_paths);
|
|
171
|
+
result.warnings.push(...indexed.warnings);
|
|
172
|
+
result.errors.push(...indexed.errors);
|
|
173
|
+
} catch (err) {
|
|
174
|
+
result.errors.push(`${entry.name}: ${String(err)}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (!entries.length) result.index_paths.push(await globalIndex());
|
|
178
|
+
result.index_paths = [...new Set(result.index_paths)];
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
async function createMetaRecord(entry, articlePath, kind, body) {
|
|
182
|
+
if (!META_KINDS.includes(kind)) throw new InvalidTargetError(kind, "unknown metadata template");
|
|
183
|
+
let mirror;
|
|
184
|
+
if (articlePath !== void 0) {
|
|
185
|
+
validateMetaArticlePath(articlePath);
|
|
186
|
+
const sourceRoot = await fs.realpath(entry.path);
|
|
187
|
+
const source = await fs.realpath(path2.resolve(entry.path, articlePath));
|
|
188
|
+
if (!within(sourceRoot, source) || !(await fs.stat(source)).isFile()) {
|
|
189
|
+
throw new InvalidTargetError(articlePath, "article must be a file within its collection");
|
|
190
|
+
}
|
|
191
|
+
mirror = await ensureArticleMeta(entry, articlePath);
|
|
192
|
+
} else await ensureCollectionMeta(entry);
|
|
193
|
+
const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
194
|
+
const id = `${capturedAt.replace(/[-:.]/g, "")}-${randomUUID().slice(0, 8)}`;
|
|
195
|
+
const file = await checked(path2.join(collectionRoot(entry.name), ".meta", "records", `${id}.md`));
|
|
196
|
+
await fs.mkdir(path2.dirname(file), { recursive: true });
|
|
197
|
+
const header = {
|
|
198
|
+
kind,
|
|
199
|
+
collection: entry.name,
|
|
200
|
+
...articlePath ? { article_path: articlePath } : {},
|
|
201
|
+
captured_at: capturedAt
|
|
202
|
+
};
|
|
203
|
+
await fs.writeFile(file, `---
|
|
204
|
+
${stringify(header)}---
|
|
205
|
+
${body}`, { flag: "wx" });
|
|
206
|
+
const indexed = await reindexMeta(entry.name);
|
|
207
|
+
if (indexed.errors.length)
|
|
208
|
+
throw new DiffwikiError(
|
|
209
|
+
`Record saved at ${file}, but indexing failed: ${indexed.errors.join("; ")}. Repair with: diffwiki meta reindex ${entry.name}`
|
|
210
|
+
);
|
|
211
|
+
return {
|
|
212
|
+
id,
|
|
213
|
+
path: file,
|
|
214
|
+
target: articlePath ? `${entry.name}:${articlePath}` : entry.name,
|
|
215
|
+
...mirror ? { mirror_path: mirror } : {},
|
|
216
|
+
index_paths: indexed.index_paths
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
var START = "<!-- diffwiki:meta:start -->";
|
|
220
|
+
var END = "<!-- diffwiki:meta:end -->";
|
|
221
|
+
var within = (root, file) => file === root || file.startsWith(root + path2.sep);
|
|
222
|
+
var missing = (err) => err.code === "ENOENT";
|
|
223
|
+
function collectionRoot(name) {
|
|
224
|
+
if (!name || name.startsWith(".") || /[/\\\0]/.test(name) || name.toLowerCase() === "meta.md") {
|
|
225
|
+
throw new InvalidTargetError(name, "expected a collection name without path segments or reserved names");
|
|
226
|
+
}
|
|
227
|
+
return path2.resolve(metaDir(), name);
|
|
228
|
+
}
|
|
229
|
+
async function checked(file) {
|
|
230
|
+
const root = path2.resolve(metaDir());
|
|
231
|
+
const absolute = path2.resolve(file);
|
|
232
|
+
if (!within(root, absolute)) throw new InvalidTargetError(file, "path escapes the metadata root");
|
|
233
|
+
const parts = path2.relative(root, absolute).split(path2.sep).filter(Boolean);
|
|
234
|
+
let current = root;
|
|
235
|
+
for (const part of ["", ...parts]) {
|
|
236
|
+
current = part ? path2.join(current, part) : current;
|
|
237
|
+
try {
|
|
238
|
+
if ((await fs.lstat(current)).isSymbolicLink()) throw new DiffwikiError(`metadata path is a symlink: ${current}`);
|
|
239
|
+
} catch (err) {
|
|
240
|
+
if (missing(err)) break;
|
|
241
|
+
throw err;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return absolute;
|
|
245
|
+
}
|
|
246
|
+
async function seed(file, title) {
|
|
247
|
+
await checked(file);
|
|
248
|
+
await fs.mkdir(path2.dirname(file), { recursive: true });
|
|
249
|
+
try {
|
|
250
|
+
await fs.writeFile(file, `# ${title}
|
|
251
|
+
|
|
252
|
+
${START}
|
|
253
|
+
No source history captured.
|
|
254
|
+
${END}
|
|
255
|
+
`, { flag: "wx" });
|
|
256
|
+
return true;
|
|
257
|
+
} catch (err) {
|
|
258
|
+
if (err.code === "EEXIST") return false;
|
|
259
|
+
throw err;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
async function listing(file, title, lines) {
|
|
263
|
+
await seed(file, title);
|
|
264
|
+
const raw = await fs.readFile(await checked(file), "utf8");
|
|
265
|
+
const block = `${START}
|
|
266
|
+
${lines.length ? lines.join("\n") : "No source history captured."}
|
|
267
|
+
${END}`;
|
|
268
|
+
const start = raw.indexOf(START);
|
|
269
|
+
const end = raw.indexOf(END, start);
|
|
270
|
+
if (start >= 0 !== end >= 0) throw new DiffwikiError(`incomplete generated section in ${file}`);
|
|
271
|
+
const next = start >= 0 ? raw.slice(0, start) + block + raw.slice(end + END.length) : `${raw}
|
|
272
|
+
${block}
|
|
273
|
+
`;
|
|
274
|
+
if (next === raw) return;
|
|
275
|
+
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
276
|
+
try {
|
|
277
|
+
await fs.writeFile(await checked(temporary), next, { flag: "wx" });
|
|
278
|
+
await checked(file);
|
|
279
|
+
await fs.rename(temporary, file);
|
|
280
|
+
} finally {
|
|
281
|
+
await fs.rm(temporary, { force: true });
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function link(from, to, label) {
|
|
285
|
+
const href = path2.relative(path2.dirname(from), to).split(path2.sep).map(encodeURIComponent).join("/");
|
|
286
|
+
return `[${label.replaceAll("[", "").replaceAll("]", "").replace(/[\r\n]/g, "")}](${href})`;
|
|
287
|
+
}
|
|
288
|
+
async function markdownFiles(root, rel = "") {
|
|
289
|
+
const out = [];
|
|
290
|
+
for (const item of (await fs.readdir(path2.join(root, rel), { withFileTypes: true })).sort(
|
|
291
|
+
(a, b) => a.name.localeCompare(b.name)
|
|
292
|
+
)) {
|
|
293
|
+
if (item.name.startsWith(".")) continue;
|
|
294
|
+
const file = rel ? `${rel}/${item.name}` : item.name;
|
|
295
|
+
if (item.isDirectory()) out.push(...await markdownFiles(root, file));
|
|
296
|
+
else if (item.isFile() && /\.mdx?$/i.test(file)) out.push(file);
|
|
297
|
+
}
|
|
298
|
+
return out;
|
|
299
|
+
}
|
|
300
|
+
async function globalIndex() {
|
|
301
|
+
await fs.mkdir(await checked(metaDir()), { recursive: true });
|
|
302
|
+
const index = path2.join(metaDir(), "META.md");
|
|
303
|
+
const lines = [];
|
|
304
|
+
for (const item of (await fs.readdir(metaDir(), { withFileTypes: true })).sort(
|
|
305
|
+
(a, b) => a.name.localeCompare(b.name)
|
|
306
|
+
)) {
|
|
307
|
+
if (!item.isDirectory() || item.name.startsWith(".")) continue;
|
|
308
|
+
const collectionIndex = path2.join(collectionRoot(item.name), ".meta", "META.md");
|
|
309
|
+
try {
|
|
310
|
+
await fs.access(await checked(collectionIndex));
|
|
311
|
+
lines.push(`- ${link(index, collectionIndex, item.name)}`);
|
|
312
|
+
} catch (err) {
|
|
313
|
+
if (!missing(err)) throw err;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
await listing(index, "Source collections", lines);
|
|
317
|
+
return index;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/registry.ts
|
|
35
321
|
function isMissing(err) {
|
|
36
322
|
return err?.code === "ENOENT";
|
|
37
323
|
}
|
|
38
324
|
async function readRegistry() {
|
|
39
325
|
try {
|
|
40
|
-
const parsed = JSON.parse(await
|
|
326
|
+
const parsed = JSON.parse(await fs2.readFile(registryPath(), "utf8"));
|
|
41
327
|
return { version: parsed.version ?? 1, collections: parsed.collections ?? [] };
|
|
42
328
|
} catch (err) {
|
|
43
329
|
if (isMissing(err)) return { version: 1, collections: [] };
|
|
@@ -45,8 +331,8 @@ async function readRegistry() {
|
|
|
45
331
|
}
|
|
46
332
|
}
|
|
47
333
|
async function writeRegistry(registry) {
|
|
48
|
-
await
|
|
49
|
-
await
|
|
334
|
+
await fs2.mkdir(resolveHome(), { recursive: true });
|
|
335
|
+
await fs2.writeFile(registryPath(), `${JSON.stringify(registry, null, 2)}
|
|
50
336
|
`, "utf8");
|
|
51
337
|
}
|
|
52
338
|
async function registerCollection(entry) {
|
|
@@ -58,6 +344,13 @@ async function registerCollection(entry) {
|
|
|
58
344
|
registry.collections.push(entry);
|
|
59
345
|
}
|
|
60
346
|
await writeRegistry(registry);
|
|
347
|
+
try {
|
|
348
|
+
await ensureCollectionMeta(entry);
|
|
349
|
+
} catch (err) {
|
|
350
|
+
throw new DiffwikiError(
|
|
351
|
+
`Collection ${entry.name} registered, but metadata setup failed: ${String(err)}. Repair with: diffwiki meta init ${entry.name}`
|
|
352
|
+
);
|
|
353
|
+
}
|
|
61
354
|
}
|
|
62
355
|
async function unregisterCollection(name) {
|
|
63
356
|
const registry = await readRegistry();
|
|
@@ -65,61 +358,10 @@ async function unregisterCollection(name) {
|
|
|
65
358
|
await writeRegistry(registry);
|
|
66
359
|
}
|
|
67
360
|
|
|
68
|
-
// src/errors.ts
|
|
69
|
-
var DiffwikiError = class extends Error {
|
|
70
|
-
constructor(message) {
|
|
71
|
-
super(message);
|
|
72
|
-
this.name = new.target.name;
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
var CollectionExistsError = class extends DiffwikiError {
|
|
76
|
-
constructor(name) {
|
|
77
|
-
super(`collection "${name}" already exists`);
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
var CollectionNotFoundError = class extends DiffwikiError {
|
|
81
|
-
constructor(name) {
|
|
82
|
-
super(`collection "${name}" not found`);
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
var NoDefaultCollectionError = class extends DiffwikiError {
|
|
86
|
-
constructor() {
|
|
87
|
-
super("no default collection set (use: diffwiki config-set defaultCollection <name>)");
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
var ArticleNotFoundError = class extends DiffwikiError {
|
|
91
|
-
constructor(target) {
|
|
92
|
-
super(`article "${target}" not found`);
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
var InvalidTargetError = class extends DiffwikiError {
|
|
96
|
-
constructor(target, expected) {
|
|
97
|
-
super(`invalid target "${target}" \u2014 ${expected}`);
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
var PluginError = class extends DiffwikiError {
|
|
101
|
-
};
|
|
102
|
-
var WorktreeNotAllowedError = class extends DiffwikiError {
|
|
103
|
-
constructor(root) {
|
|
104
|
-
super(`refusing to add a linked worktree \u2014 add the source repository instead (${root})`);
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
var CollectionAlreadyAddedError = class extends DiffwikiError {
|
|
108
|
-
constructor(name) {
|
|
109
|
-
super(`this repository is already added as collection "${name}"`);
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
var InvalidExportSelectionError = class extends DiffwikiError {
|
|
113
|
-
constructor(problems) {
|
|
114
|
-
super(`export aborted \u2014 ${problems.length} selection problem(s):
|
|
115
|
-
${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
|
|
119
361
|
// src/collections.ts
|
|
120
362
|
async function fireHook(event, collection) {
|
|
121
363
|
try {
|
|
122
|
-
const { emitPluginEvent: emitPluginEvent2 } = await import("./events-
|
|
364
|
+
const { emitPluginEvent: emitPluginEvent2 } = await import("./events-UG4JPZDZ.js");
|
|
123
365
|
await emitPluginEvent2(event, collection);
|
|
124
366
|
} catch {
|
|
125
367
|
}
|
|
@@ -136,7 +378,7 @@ async function findCollectionById(id) {
|
|
|
136
378
|
async function createCollection(name, opts) {
|
|
137
379
|
if (await findCollection(name)) throw new CollectionExistsError(name);
|
|
138
380
|
const dir = collectionDir(name);
|
|
139
|
-
await
|
|
381
|
+
await fs3.mkdir(dir, { recursive: true });
|
|
140
382
|
const entry = {
|
|
141
383
|
name,
|
|
142
384
|
type: "global",
|
|
@@ -153,20 +395,20 @@ async function removeCollection(name) {
|
|
|
153
395
|
if (!entry) throw new CollectionNotFoundError(name);
|
|
154
396
|
await unregisterCollection(name);
|
|
155
397
|
if (entry.type === "global") {
|
|
156
|
-
await
|
|
398
|
+
await fs3.rm(entry.path, { recursive: true, force: true });
|
|
157
399
|
}
|
|
158
400
|
void fireHook("collection-removed", name);
|
|
159
401
|
return entry;
|
|
160
402
|
}
|
|
161
403
|
|
|
162
404
|
// src/plugins/registry.ts
|
|
163
|
-
import
|
|
405
|
+
import fs4 from "fs/promises";
|
|
164
406
|
function isMissing2(err) {
|
|
165
407
|
return err?.code === "ENOENT";
|
|
166
408
|
}
|
|
167
409
|
async function readPluginRegistry() {
|
|
168
410
|
try {
|
|
169
|
-
const parsed = JSON.parse(await
|
|
411
|
+
const parsed = JSON.parse(await fs4.readFile(pluginsRegistryPath(), "utf8"));
|
|
170
412
|
return { version: parsed.version ?? 1, plugins: parsed.plugins ?? [] };
|
|
171
413
|
} catch (err) {
|
|
172
414
|
if (isMissing2(err)) return { version: 1, plugins: [] };
|
|
@@ -174,8 +416,8 @@ async function readPluginRegistry() {
|
|
|
174
416
|
}
|
|
175
417
|
}
|
|
176
418
|
async function writePluginRegistry(registry) {
|
|
177
|
-
await
|
|
178
|
-
await
|
|
419
|
+
await fs4.mkdir(resolveHome(), { recursive: true });
|
|
420
|
+
await fs4.writeFile(pluginsRegistryPath(), `${JSON.stringify(registry, null, 2)}
|
|
179
421
|
`, "utf8");
|
|
180
422
|
}
|
|
181
423
|
async function upsertPlugin(record) {
|
|
@@ -209,7 +451,7 @@ async function findPluginRecord(name) {
|
|
|
209
451
|
|
|
210
452
|
// src/plugins/host.ts
|
|
211
453
|
import { spawn } from "child_process";
|
|
212
|
-
import
|
|
454
|
+
import path3, { basename } from "path";
|
|
213
455
|
|
|
214
456
|
// src/logger.ts
|
|
215
457
|
import { getLogger as logtapeGetLogger } from "@logtape/logtape";
|
|
@@ -346,7 +588,7 @@ function mapHits(result, collections) {
|
|
|
346
588
|
if (typeof h.collection !== "string" || typeof h.relPath !== "string") continue;
|
|
347
589
|
const title = typeof h.title === "string" ? h.title : h.relPath;
|
|
348
590
|
const coll = collections.find((c) => c.name === h.collection);
|
|
349
|
-
const abs = coll ?
|
|
591
|
+
const abs = coll ? path3.join(coll.path, h.relPath) : h.relPath;
|
|
350
592
|
out.push({
|
|
351
593
|
collection: h.collection,
|
|
352
594
|
path: abs,
|
|
@@ -473,24 +715,23 @@ async function emitPluginEvent(event, collection, relPath) {
|
|
|
473
715
|
|
|
474
716
|
export {
|
|
475
717
|
NATIVE_ENGINE,
|
|
476
|
-
DiffwikiError,
|
|
477
|
-
CollectionExistsError,
|
|
478
|
-
CollectionNotFoundError,
|
|
479
|
-
NoDefaultCollectionError,
|
|
480
|
-
ArticleNotFoundError,
|
|
481
|
-
InvalidTargetError,
|
|
482
|
-
PluginError,
|
|
483
|
-
WorktreeNotAllowedError,
|
|
484
|
-
CollectionAlreadyAddedError,
|
|
485
|
-
InvalidExportSelectionError,
|
|
486
718
|
resolveHome,
|
|
487
719
|
registryPath,
|
|
488
720
|
configPath,
|
|
489
721
|
collectionsDir,
|
|
490
722
|
templatesDir,
|
|
723
|
+
metaDir,
|
|
491
724
|
collectionDir,
|
|
492
725
|
pluginsDir,
|
|
493
726
|
pluginsRegistryPath,
|
|
727
|
+
META_KINDS,
|
|
728
|
+
validateMetaArticlePath,
|
|
729
|
+
ensureCollectionMeta,
|
|
730
|
+
ensureArticleMeta,
|
|
731
|
+
mirrorSavedArticle,
|
|
732
|
+
reindexMeta,
|
|
733
|
+
initMeta,
|
|
734
|
+
createMetaRecord,
|
|
494
735
|
readRegistry,
|
|
495
736
|
writeRegistry,
|
|
496
737
|
registerCollection,
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DiffwikiError
|
|
3
|
+
} from "./chunk-FJ5XD2JN.js";
|
|
4
|
+
|
|
1
5
|
// src/frontmatter.ts
|
|
2
6
|
import matter from "gray-matter";
|
|
3
|
-
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
7
|
+
import { parse as parseYaml, stringify as stringifyYaml, parseDocument, isScalar } from "yaml";
|
|
4
8
|
var YAML_ENGINE = {
|
|
5
9
|
parse: (input) => {
|
|
6
|
-
const data = parseYaml(input);
|
|
10
|
+
const data = parseYaml(input.replace(/\r\n?/g, "\n"));
|
|
7
11
|
return data && typeof data === "object" ? data : {};
|
|
8
12
|
},
|
|
9
13
|
stringify: (data) => stringifyYaml(data)
|
|
@@ -47,6 +51,31 @@ var FRONTMATTER_BLOCK_RE = /^\uFEFF?[ \t]*---\r?\n[\s\S]*?\r?\n---[ \t]*(?:\r?\n
|
|
|
47
51
|
function stripFrontmatter(raw) {
|
|
48
52
|
return raw.replace(FRONTMATTER_BLOCK_RE, "");
|
|
49
53
|
}
|
|
54
|
+
function patchArticle(raw, fields, body) {
|
|
55
|
+
const fence = /^(\uFEFF?[ \t]*---[ \t]*\r?\n)([\s\S]*?)(\r?\n---[ \t]*(?:\r?\n|$))/.exec(raw);
|
|
56
|
+
if (Object.keys(fields).length === 0) return fence ? fence[0] + (body ?? raw.slice(fence[0].length)) : body ?? raw;
|
|
57
|
+
const originalYaml = fence?.[2] ?? "";
|
|
58
|
+
const doc = parseDocument(originalYaml);
|
|
59
|
+
if (doc.errors.length)
|
|
60
|
+
throw new DiffwikiError("Cannot update article with invalid YAML frontmatter. Fix its header first.");
|
|
61
|
+
const titleNode = doc.get("title", true);
|
|
62
|
+
if (fence && body === void 0 && Object.keys(fields).length === 1 && typeof fields.title === "string" && !/[\r\n]/.test(fields.title) && isScalar(titleNode) && titleNode.range) {
|
|
63
|
+
const [start, end] = titleNode.range;
|
|
64
|
+
if (!/[\r\n]/.test(originalYaml.slice(start, end))) {
|
|
65
|
+
const scalar = stringifyYaml(fields.title, { lineWidth: 0 }).trimEnd();
|
|
66
|
+
return fence[1] + originalYaml.slice(0, start) + scalar + originalYaml.slice(end) + fence[3] + raw.slice(fence[0].length);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
70
|
+
if (value === void 0) doc.delete(key);
|
|
71
|
+
else doc.set(key, value);
|
|
72
|
+
}
|
|
73
|
+
const newline = fence?.[1].endsWith("\r\n") ? "\r\n" : "\n";
|
|
74
|
+
const yaml = doc.toString({ lineWidth: 0 }).replace(/\n$/, "").replace(/\r?\n/g, newline);
|
|
75
|
+
const content = body ?? (fence ? raw.slice(fence[0].length) : raw);
|
|
76
|
+
const close = fence?.[3] ?? `${newline}---${newline}`;
|
|
77
|
+
return (fence?.[1] ?? `---${newline}`) + yaml + close + (body && !close.endsWith("\n") ? newline : "") + content;
|
|
78
|
+
}
|
|
50
79
|
function parseFrontmatter(raw) {
|
|
51
80
|
try {
|
|
52
81
|
const { data, content } = matter(raw, MATTER_OPTS);
|
|
@@ -266,6 +295,7 @@ function definePlugin(def) {
|
|
|
266
295
|
|
|
267
296
|
export {
|
|
268
297
|
stripFrontmatter,
|
|
298
|
+
patchArticle,
|
|
269
299
|
parseFrontmatter,
|
|
270
300
|
parseArticle,
|
|
271
301
|
serializeArticle,
|
|
@@ -569,6 +569,8 @@ interface SerializableArticle {
|
|
|
569
569
|
* span. Returns the input unchanged when there is no frontmatter.
|
|
570
570
|
*/
|
|
571
571
|
declare function stripFrontmatter(raw: string): string;
|
|
572
|
+
/** Edit requested fields while preserving unknown YAML keys, comments, and the untouched body. */
|
|
573
|
+
declare function patchArticle(raw: string, fields: Record<string, unknown>, body?: string): string;
|
|
572
574
|
/**
|
|
573
575
|
* Parse a document's YAML frontmatter into a raw key/value object (every key,
|
|
574
576
|
* not just the article axes), plus the body. Bundler-safe via the pinned `yaml`
|
|
@@ -582,4 +584,4 @@ declare function parseFrontmatter(raw: string): {
|
|
|
582
584
|
declare function parseArticle(raw: string): ParsedArticle;
|
|
583
585
|
declare function serializeArticle(article: SerializableArticle): string;
|
|
584
586
|
|
|
585
|
-
export { type
|
|
587
|
+
export { type PluginDependency as $, type Audience as A, type PluginHealth as B, type Config as C, type Diagnostic as D, type PluginReadiness as E, type ProjectCollection as F, type GraphEdge as G, type HomeCard as H, type ProjectContext as I, type SearchExpansion as J, type SerializableArticle as K, type LinkGraph as L, type SiteCopyright as M, NATIVE_ENGINE as N, type SiteHome as O, type PublishStatus as P, type QueryHit as Q, type Registry as R, type SiteConfig as S, type TreeNode as T, parseArticle as U, parseFrontmatter as V, patchArticle as W, serializeArticle as X, stripFrontmatter as Y, type PluginDefinition as Z, type DefinedPlugin as _, type CollectionEntry as a, type ResolvedDependency as a0, type RunProcessOptions as a1, type EventHandlerResult as a2, type EventParams as a3, type HealthHandlerResult as a4, type HealthParams as a5, type IndexHandlerResult as a6, type IndexParams as a7, type LogLevel as a8, type PluginCapabilities$1 as a9, type PluginHandlers as aa, type PluginLogFn as ab, type SearchHandlerHit as ac, type SearchHandlerResult as ad, type SearchParams as ae, type SetupHandlerResult as af, type SetupParams as ag, type WireCollection as ah, type WireDiagnostic as ai, type WireReadiness as aj, type Article as b, type RepoConfig as c, type ProjectConfig as d, type CollectionInfo as e, type CollectionTree as f, type PageData as g, type ArticleNode as h, type QueryOptions as i, type ParsedArticle as j, type ArticleContent as k, type TocItem as l, type LocalGraph as m, type SearchMode as n, type SearchProvider as o, type PluginRecord as p, type PluginEvent as q, type PluginKind as r, type PluginRegistryFile as s, type CollectionType as t, type DiagnosticLevel as u, type DocData as v, type GraphNode as w, type ListingEntry as x, type NodeMeta as y, type PluginCapabilities as z };
|
|
@@ -569,6 +569,8 @@ interface SerializableArticle {
|
|
|
569
569
|
* span. Returns the input unchanged when there is no frontmatter.
|
|
570
570
|
*/
|
|
571
571
|
declare function stripFrontmatter(raw: string): string;
|
|
572
|
+
/** Edit requested fields while preserving unknown YAML keys, comments, and the untouched body. */
|
|
573
|
+
declare function patchArticle(raw: string, fields: Record<string, unknown>, body?: string): string;
|
|
572
574
|
/**
|
|
573
575
|
* Parse a document's YAML frontmatter into a raw key/value object (every key,
|
|
574
576
|
* not just the article axes), plus the body. Bundler-safe via the pinned `yaml`
|
|
@@ -582,4 +584,4 @@ declare function parseFrontmatter(raw: string): {
|
|
|
582
584
|
declare function parseArticle(raw: string): ParsedArticle;
|
|
583
585
|
declare function serializeArticle(article: SerializableArticle): string;
|
|
584
586
|
|
|
585
|
-
export { type
|
|
587
|
+
export { type PluginDependency as $, type Audience as A, type PluginHealth as B, type Config as C, type Diagnostic as D, type PluginReadiness as E, type ProjectCollection as F, type GraphEdge as G, type HomeCard as H, type ProjectContext as I, type SearchExpansion as J, type SerializableArticle as K, type LinkGraph as L, type SiteCopyright as M, NATIVE_ENGINE as N, type SiteHome as O, type PublishStatus as P, type QueryHit as Q, type Registry as R, type SiteConfig as S, type TreeNode as T, parseArticle as U, parseFrontmatter as V, patchArticle as W, serializeArticle as X, stripFrontmatter as Y, type PluginDefinition as Z, type DefinedPlugin as _, type CollectionEntry as a, type ResolvedDependency as a0, type RunProcessOptions as a1, type EventHandlerResult as a2, type EventParams as a3, type HealthHandlerResult as a4, type HealthParams as a5, type IndexHandlerResult as a6, type IndexParams as a7, type LogLevel as a8, type PluginCapabilities$1 as a9, type PluginHandlers as aa, type PluginLogFn as ab, type SearchHandlerHit as ac, type SearchHandlerResult as ad, type SearchParams as ae, type SetupHandlerResult as af, type SetupParams as ag, type WireCollection as ah, type WireDiagnostic as ai, type WireReadiness as aj, type Article as b, type RepoConfig as c, type ProjectConfig as d, type CollectionInfo as e, type CollectionTree as f, type PageData as g, type ArticleNode as h, type QueryOptions as i, type ParsedArticle as j, type ArticleContent as k, type TocItem as l, type LocalGraph as m, type SearchMode as n, type SearchProvider as o, type PluginRecord as p, type PluginEvent as q, type PluginKind as r, type PluginRegistryFile as s, type CollectionType as t, type DiagnosticLevel as u, type DocData as v, type GraphNode as w, type ListingEntry as x, type NodeMeta as y, type PluginCapabilities as z };
|