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
package/dist/index.cjs
CHANGED
|
@@ -113,6 +113,9 @@ function collectionsDir() {
|
|
|
113
113
|
function templatesDir() {
|
|
114
114
|
return import_node_path.default.join(resolveHome(), "templates");
|
|
115
115
|
}
|
|
116
|
+
function metaDir() {
|
|
117
|
+
return import_node_path.default.join(resolveHome(), "meta");
|
|
118
|
+
}
|
|
116
119
|
function collectionDir(name) {
|
|
117
120
|
return import_node_path.default.join(collectionsDir(), name);
|
|
118
121
|
}
|
|
@@ -131,13 +134,295 @@ var init_paths = __esm({
|
|
|
131
134
|
}
|
|
132
135
|
});
|
|
133
136
|
|
|
137
|
+
// src/meta.ts
|
|
138
|
+
function validateMetaArticlePath(file) {
|
|
139
|
+
if (!/\.mdx?$/i.test(file) || import_node_path3.default.isAbsolute(file) || /[\\\0]/.test(file) || file.split("/").some((part) => !part || part === "." || part === ".." || part.startsWith("."))) {
|
|
140
|
+
throw new InvalidTargetError(file, "expected an exact relative .md/.mdx file path without traversal");
|
|
141
|
+
}
|
|
142
|
+
return file;
|
|
143
|
+
}
|
|
144
|
+
async function ensureCollectionMeta(entry) {
|
|
145
|
+
const root = collectionRoot(entry.name);
|
|
146
|
+
const source = import_node_path3.default.resolve(entry.path);
|
|
147
|
+
if (within(source, import_node_path3.default.resolve(metaDir())) || within(import_node_path3.default.resolve(metaDir()), source)) {
|
|
148
|
+
throw new InvalidTargetError(entry.path, "collection content overlaps the metadata store");
|
|
149
|
+
}
|
|
150
|
+
const index = import_node_path3.default.join(root, ".meta", "META.md");
|
|
151
|
+
await seed(index, `Sources for ${entry.name}`);
|
|
152
|
+
await globalIndex();
|
|
153
|
+
return index;
|
|
154
|
+
}
|
|
155
|
+
async function ensureArticleMeta(entry, articlePath) {
|
|
156
|
+
const relative = validateMetaArticlePath(articlePath);
|
|
157
|
+
await ensureCollectionMeta(entry);
|
|
158
|
+
const mirror = import_node_path3.default.join(collectionRoot(entry.name), relative);
|
|
159
|
+
if (await seed(mirror, `Sources for ${entry.name}:${relative}`)) await reindexMeta(entry.name);
|
|
160
|
+
return mirror;
|
|
161
|
+
}
|
|
162
|
+
async function mirrorSavedArticle(entry, file) {
|
|
163
|
+
try {
|
|
164
|
+
await ensureArticleMeta(entry, import_node_path3.default.relative(entry.path, file).split(import_node_path3.default.sep).join("/"));
|
|
165
|
+
} catch (err) {
|
|
166
|
+
throw new DiffwikiError(
|
|
167
|
+
`Article saved at ${file}, but metadata setup failed: ${String(err)}. Repair with: diffwiki meta init ${entry.name}`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async function reindexMeta(collection) {
|
|
172
|
+
await import_promises3.default.mkdir(await checked(metaDir()), { recursive: true });
|
|
173
|
+
const result = { created: [], kept: [], index_paths: [], warnings: [], errors: [] };
|
|
174
|
+
const names = collection ? [collection] : (await import_promises3.default.readdir(metaDir(), { withFileTypes: true })).filter((item) => item.isDirectory() && !item.name.startsWith(".")).map((item) => item.name).sort();
|
|
175
|
+
for (const name of names) {
|
|
176
|
+
try {
|
|
177
|
+
const root = await checked(collectionRoot(name));
|
|
178
|
+
const index = import_node_path3.default.join(root, ".meta", "META.md");
|
|
179
|
+
await import_promises3.default.access(await checked(index));
|
|
180
|
+
const pages = await markdownFiles(root);
|
|
181
|
+
const recordDir = await checked(import_node_path3.default.join(root, ".meta", "records"));
|
|
182
|
+
let records = [];
|
|
183
|
+
try {
|
|
184
|
+
records = (await import_promises3.default.readdir(recordDir)).filter((file) => file.endsWith(".md")).sort();
|
|
185
|
+
} catch (err) {
|
|
186
|
+
if (!missing(err)) throw err;
|
|
187
|
+
}
|
|
188
|
+
const pageLines = new Map(pages.map((file) => [file, []]));
|
|
189
|
+
const sourceLines = [];
|
|
190
|
+
const dreamLines = [];
|
|
191
|
+
for (const record of records) {
|
|
192
|
+
const file = await checked(import_node_path3.default.join(recordDir, record));
|
|
193
|
+
try {
|
|
194
|
+
const raw = await import_promises3.default.readFile(file, "utf8");
|
|
195
|
+
const front = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(raw);
|
|
196
|
+
if (!front) throw new Error("missing record header");
|
|
197
|
+
const doc = (0, import_yaml2.parseDocument)(front[1]);
|
|
198
|
+
if (doc.errors.length) throw new Error("invalid YAML header");
|
|
199
|
+
const data = doc.toJS();
|
|
200
|
+
if (!data || !META_KINDS.includes(data.kind) || data.collection !== name || typeof data.captured_at !== "string") {
|
|
201
|
+
throw new Error("invalid kind, collection, or capture time");
|
|
202
|
+
}
|
|
203
|
+
const article = data.article_path === void 0 ? void 0 : validateMetaArticlePath(String(data.article_path));
|
|
204
|
+
const label = `${data.kind} \xB7 ${record.replace(/\.md$/, "")}`;
|
|
205
|
+
const bucket = data.kind === "dream-learning" ? dreamLines : sourceLines;
|
|
206
|
+
bucket.push(`- ${link(index, file, label)}${article ? ` \u2014 \`${article}\`` : " \u2014 collection"}`);
|
|
207
|
+
if (article) {
|
|
208
|
+
if (!pageLines.has(article))
|
|
209
|
+
result.warnings.push(`${file}: no manifest for ${article}; run meta init if the article exists`);
|
|
210
|
+
else pageLines.get(article).push(`- ${link(import_node_path3.default.join(root, article), file, label)}`);
|
|
211
|
+
}
|
|
212
|
+
} catch (err) {
|
|
213
|
+
result.warnings.push(`${file}: ${String(err)}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
for (const [page, lines] of pageLines) {
|
|
217
|
+
const file = import_node_path3.default.join(root, page);
|
|
218
|
+
await listing(file, `Sources for ${name}:${page}`, [
|
|
219
|
+
`Collection: ${link(file, index, "collection sources")}`,
|
|
220
|
+
"",
|
|
221
|
+
...lines.length ? lines : ["No page source history captured."]
|
|
222
|
+
]);
|
|
223
|
+
}
|
|
224
|
+
await listing(index, `Sources for ${name}`, [
|
|
225
|
+
"## Raw sources",
|
|
226
|
+
"",
|
|
227
|
+
...sourceLines.length ? sourceLines : ["No raw sources captured."],
|
|
228
|
+
"",
|
|
229
|
+
"## Dream learnings",
|
|
230
|
+
"",
|
|
231
|
+
...dreamLines.length ? dreamLines : ["No dream learnings captured."],
|
|
232
|
+
"",
|
|
233
|
+
"## Pages",
|
|
234
|
+
"",
|
|
235
|
+
...pages.map((page) => `- ${link(index, import_node_path3.default.join(root, page), page)}`)
|
|
236
|
+
]);
|
|
237
|
+
result.index_paths.push(index);
|
|
238
|
+
} catch (err) {
|
|
239
|
+
result.errors.push(`${name}: ${String(err)}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
result.index_paths.push(await globalIndex());
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
245
|
+
async function initMeta(entries) {
|
|
246
|
+
const result = { created: [], kept: [], index_paths: [], warnings: [], errors: [] };
|
|
247
|
+
for (const entry of entries) {
|
|
248
|
+
try {
|
|
249
|
+
const files = await markdownFiles(entry.path);
|
|
250
|
+
await ensureCollectionMeta(entry);
|
|
251
|
+
for (const file of files) {
|
|
252
|
+
const mirror = import_node_path3.default.join(collectionRoot(entry.name), validateMetaArticlePath(file));
|
|
253
|
+
(await seed(mirror, `Sources for ${entry.name}:${file}`) ? result.created : result.kept).push(mirror);
|
|
254
|
+
}
|
|
255
|
+
const indexed = await reindexMeta(entry.name);
|
|
256
|
+
result.index_paths.push(...indexed.index_paths);
|
|
257
|
+
result.warnings.push(...indexed.warnings);
|
|
258
|
+
result.errors.push(...indexed.errors);
|
|
259
|
+
} catch (err) {
|
|
260
|
+
result.errors.push(`${entry.name}: ${String(err)}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (!entries.length) result.index_paths.push(await globalIndex());
|
|
264
|
+
result.index_paths = [...new Set(result.index_paths)];
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
267
|
+
async function createMetaRecord(entry, articlePath, kind, body) {
|
|
268
|
+
if (!META_KINDS.includes(kind)) throw new InvalidTargetError(kind, "unknown metadata template");
|
|
269
|
+
let mirror;
|
|
270
|
+
if (articlePath !== void 0) {
|
|
271
|
+
validateMetaArticlePath(articlePath);
|
|
272
|
+
const sourceRoot = await import_promises3.default.realpath(entry.path);
|
|
273
|
+
const source = await import_promises3.default.realpath(import_node_path3.default.resolve(entry.path, articlePath));
|
|
274
|
+
if (!within(sourceRoot, source) || !(await import_promises3.default.stat(source)).isFile()) {
|
|
275
|
+
throw new InvalidTargetError(articlePath, "article must be a file within its collection");
|
|
276
|
+
}
|
|
277
|
+
mirror = await ensureArticleMeta(entry, articlePath);
|
|
278
|
+
} else await ensureCollectionMeta(entry);
|
|
279
|
+
const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
280
|
+
const id = `${capturedAt.replace(/[-:.]/g, "")}-${(0, import_node_crypto.randomUUID)().slice(0, 8)}`;
|
|
281
|
+
const file = await checked(import_node_path3.default.join(collectionRoot(entry.name), ".meta", "records", `${id}.md`));
|
|
282
|
+
await import_promises3.default.mkdir(import_node_path3.default.dirname(file), { recursive: true });
|
|
283
|
+
const header = {
|
|
284
|
+
kind,
|
|
285
|
+
collection: entry.name,
|
|
286
|
+
...articlePath ? { article_path: articlePath } : {},
|
|
287
|
+
captured_at: capturedAt
|
|
288
|
+
};
|
|
289
|
+
await import_promises3.default.writeFile(file, `---
|
|
290
|
+
${(0, import_yaml2.stringify)(header)}---
|
|
291
|
+
${body}`, { flag: "wx" });
|
|
292
|
+
const indexed = await reindexMeta(entry.name);
|
|
293
|
+
if (indexed.errors.length)
|
|
294
|
+
throw new DiffwikiError(
|
|
295
|
+
`Record saved at ${file}, but indexing failed: ${indexed.errors.join("; ")}. Repair with: diffwiki meta reindex ${entry.name}`
|
|
296
|
+
);
|
|
297
|
+
return {
|
|
298
|
+
id,
|
|
299
|
+
path: file,
|
|
300
|
+
target: articlePath ? `${entry.name}:${articlePath}` : entry.name,
|
|
301
|
+
...mirror ? { mirror_path: mirror } : {},
|
|
302
|
+
index_paths: indexed.index_paths
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function collectionRoot(name) {
|
|
306
|
+
if (!name || name.startsWith(".") || /[/\\\0]/.test(name) || name.toLowerCase() === "meta.md") {
|
|
307
|
+
throw new InvalidTargetError(name, "expected a collection name without path segments or reserved names");
|
|
308
|
+
}
|
|
309
|
+
return import_node_path3.default.resolve(metaDir(), name);
|
|
310
|
+
}
|
|
311
|
+
async function checked(file) {
|
|
312
|
+
const root = import_node_path3.default.resolve(metaDir());
|
|
313
|
+
const absolute = import_node_path3.default.resolve(file);
|
|
314
|
+
if (!within(root, absolute)) throw new InvalidTargetError(file, "path escapes the metadata root");
|
|
315
|
+
const parts = import_node_path3.default.relative(root, absolute).split(import_node_path3.default.sep).filter(Boolean);
|
|
316
|
+
let current = root;
|
|
317
|
+
for (const part of ["", ...parts]) {
|
|
318
|
+
current = part ? import_node_path3.default.join(current, part) : current;
|
|
319
|
+
try {
|
|
320
|
+
if ((await import_promises3.default.lstat(current)).isSymbolicLink()) throw new DiffwikiError(`metadata path is a symlink: ${current}`);
|
|
321
|
+
} catch (err) {
|
|
322
|
+
if (missing(err)) break;
|
|
323
|
+
throw err;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return absolute;
|
|
327
|
+
}
|
|
328
|
+
async function seed(file, title) {
|
|
329
|
+
await checked(file);
|
|
330
|
+
await import_promises3.default.mkdir(import_node_path3.default.dirname(file), { recursive: true });
|
|
331
|
+
try {
|
|
332
|
+
await import_promises3.default.writeFile(file, `# ${title}
|
|
333
|
+
|
|
334
|
+
${START}
|
|
335
|
+
No source history captured.
|
|
336
|
+
${END}
|
|
337
|
+
`, { flag: "wx" });
|
|
338
|
+
return true;
|
|
339
|
+
} catch (err) {
|
|
340
|
+
if (err.code === "EEXIST") return false;
|
|
341
|
+
throw err;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
async function listing(file, title, lines) {
|
|
345
|
+
await seed(file, title);
|
|
346
|
+
const raw = await import_promises3.default.readFile(await checked(file), "utf8");
|
|
347
|
+
const block = `${START}
|
|
348
|
+
${lines.length ? lines.join("\n") : "No source history captured."}
|
|
349
|
+
${END}`;
|
|
350
|
+
const start = raw.indexOf(START);
|
|
351
|
+
const end = raw.indexOf(END, start);
|
|
352
|
+
if (start >= 0 !== end >= 0) throw new DiffwikiError(`incomplete generated section in ${file}`);
|
|
353
|
+
const next = start >= 0 ? raw.slice(0, start) + block + raw.slice(end + END.length) : `${raw}
|
|
354
|
+
${block}
|
|
355
|
+
`;
|
|
356
|
+
if (next === raw) return;
|
|
357
|
+
const temporary = `${file}.${(0, import_node_crypto.randomUUID)()}.tmp`;
|
|
358
|
+
try {
|
|
359
|
+
await import_promises3.default.writeFile(await checked(temporary), next, { flag: "wx" });
|
|
360
|
+
await checked(file);
|
|
361
|
+
await import_promises3.default.rename(temporary, file);
|
|
362
|
+
} finally {
|
|
363
|
+
await import_promises3.default.rm(temporary, { force: true });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function link(from, to, label) {
|
|
367
|
+
const href = import_node_path3.default.relative(import_node_path3.default.dirname(from), to).split(import_node_path3.default.sep).map(encodeURIComponent).join("/");
|
|
368
|
+
return `[${label.replaceAll("[", "").replaceAll("]", "").replace(/[\r\n]/g, "")}](${href})`;
|
|
369
|
+
}
|
|
370
|
+
async function markdownFiles(root, rel = "") {
|
|
371
|
+
const out = [];
|
|
372
|
+
for (const item of (await import_promises3.default.readdir(import_node_path3.default.join(root, rel), { withFileTypes: true })).sort(
|
|
373
|
+
(a, b) => a.name.localeCompare(b.name)
|
|
374
|
+
)) {
|
|
375
|
+
if (item.name.startsWith(".")) continue;
|
|
376
|
+
const file = rel ? `${rel}/${item.name}` : item.name;
|
|
377
|
+
if (item.isDirectory()) out.push(...await markdownFiles(root, file));
|
|
378
|
+
else if (item.isFile() && /\.mdx?$/i.test(file)) out.push(file);
|
|
379
|
+
}
|
|
380
|
+
return out;
|
|
381
|
+
}
|
|
382
|
+
async function globalIndex() {
|
|
383
|
+
await import_promises3.default.mkdir(await checked(metaDir()), { recursive: true });
|
|
384
|
+
const index = import_node_path3.default.join(metaDir(), "META.md");
|
|
385
|
+
const lines = [];
|
|
386
|
+
for (const item of (await import_promises3.default.readdir(metaDir(), { withFileTypes: true })).sort(
|
|
387
|
+
(a, b) => a.name.localeCompare(b.name)
|
|
388
|
+
)) {
|
|
389
|
+
if (!item.isDirectory() || item.name.startsWith(".")) continue;
|
|
390
|
+
const collectionIndex = import_node_path3.default.join(collectionRoot(item.name), ".meta", "META.md");
|
|
391
|
+
try {
|
|
392
|
+
await import_promises3.default.access(await checked(collectionIndex));
|
|
393
|
+
lines.push(`- ${link(index, collectionIndex, item.name)}`);
|
|
394
|
+
} catch (err) {
|
|
395
|
+
if (!missing(err)) throw err;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
await listing(index, "Source collections", lines);
|
|
399
|
+
return index;
|
|
400
|
+
}
|
|
401
|
+
var import_promises3, import_node_path3, import_node_crypto, import_yaml2, META_KINDS, START, END, within, missing;
|
|
402
|
+
var init_meta = __esm({
|
|
403
|
+
"src/meta.ts"() {
|
|
404
|
+
"use strict";
|
|
405
|
+
import_promises3 = __toESM(require("fs/promises"), 1);
|
|
406
|
+
import_node_path3 = __toESM(require("path"), 1);
|
|
407
|
+
import_node_crypto = require("crypto");
|
|
408
|
+
import_yaml2 = require("yaml");
|
|
409
|
+
init_paths();
|
|
410
|
+
init_errors();
|
|
411
|
+
META_KINDS = ["history", "external-source-extract", "dream-learning"];
|
|
412
|
+
START = "<!-- diffwiki:meta:start -->";
|
|
413
|
+
END = "<!-- diffwiki:meta:end -->";
|
|
414
|
+
within = (root, file) => file === root || file.startsWith(root + import_node_path3.default.sep);
|
|
415
|
+
missing = (err) => err.code === "ENOENT";
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
134
419
|
// src/registry.ts
|
|
135
420
|
function isMissing(err) {
|
|
136
421
|
return err?.code === "ENOENT";
|
|
137
422
|
}
|
|
138
423
|
async function readRegistry() {
|
|
139
424
|
try {
|
|
140
|
-
const parsed = JSON.parse(await
|
|
425
|
+
const parsed = JSON.parse(await import_promises4.default.readFile(registryPath(), "utf8"));
|
|
141
426
|
return { version: parsed.version ?? 1, collections: parsed.collections ?? [] };
|
|
142
427
|
} catch (err) {
|
|
143
428
|
if (isMissing(err)) return { version: 1, collections: [] };
|
|
@@ -145,8 +430,8 @@ async function readRegistry() {
|
|
|
145
430
|
}
|
|
146
431
|
}
|
|
147
432
|
async function writeRegistry(registry) {
|
|
148
|
-
await
|
|
149
|
-
await
|
|
433
|
+
await import_promises4.default.mkdir(resolveHome(), { recursive: true });
|
|
434
|
+
await import_promises4.default.writeFile(registryPath(), `${JSON.stringify(registry, null, 2)}
|
|
150
435
|
`, "utf8");
|
|
151
436
|
}
|
|
152
437
|
async function registerCollection(entry) {
|
|
@@ -158,18 +443,27 @@ async function registerCollection(entry) {
|
|
|
158
443
|
registry.collections.push(entry);
|
|
159
444
|
}
|
|
160
445
|
await writeRegistry(registry);
|
|
446
|
+
try {
|
|
447
|
+
await ensureCollectionMeta(entry);
|
|
448
|
+
} catch (err) {
|
|
449
|
+
throw new DiffwikiError(
|
|
450
|
+
`Collection ${entry.name} registered, but metadata setup failed: ${String(err)}. Repair with: diffwiki meta init ${entry.name}`
|
|
451
|
+
);
|
|
452
|
+
}
|
|
161
453
|
}
|
|
162
454
|
async function unregisterCollection(name) {
|
|
163
455
|
const registry = await readRegistry();
|
|
164
456
|
registry.collections = registry.collections.filter((c) => c.name !== name);
|
|
165
457
|
await writeRegistry(registry);
|
|
166
458
|
}
|
|
167
|
-
var
|
|
459
|
+
var import_promises4;
|
|
168
460
|
var init_registry = __esm({
|
|
169
461
|
"src/registry.ts"() {
|
|
170
462
|
"use strict";
|
|
171
|
-
|
|
463
|
+
import_promises4 = __toESM(require("fs/promises"), 1);
|
|
172
464
|
init_paths();
|
|
465
|
+
init_meta();
|
|
466
|
+
init_errors();
|
|
173
467
|
}
|
|
174
468
|
});
|
|
175
469
|
|
|
@@ -179,7 +473,7 @@ function isMissing2(err) {
|
|
|
179
473
|
}
|
|
180
474
|
async function readPluginRegistry() {
|
|
181
475
|
try {
|
|
182
|
-
const parsed = JSON.parse(await
|
|
476
|
+
const parsed = JSON.parse(await import_promises5.default.readFile(pluginsRegistryPath(), "utf8"));
|
|
183
477
|
return { version: parsed.version ?? 1, plugins: parsed.plugins ?? [] };
|
|
184
478
|
} catch (err) {
|
|
185
479
|
if (isMissing2(err)) return { version: 1, plugins: [] };
|
|
@@ -187,8 +481,8 @@ async function readPluginRegistry() {
|
|
|
187
481
|
}
|
|
188
482
|
}
|
|
189
483
|
async function writePluginRegistry(registry) {
|
|
190
|
-
await
|
|
191
|
-
await
|
|
484
|
+
await import_promises5.default.mkdir(resolveHome(), { recursive: true });
|
|
485
|
+
await import_promises5.default.writeFile(pluginsRegistryPath(), `${JSON.stringify(registry, null, 2)}
|
|
192
486
|
`, "utf8");
|
|
193
487
|
}
|
|
194
488
|
async function upsertPlugin(record) {
|
|
@@ -219,11 +513,11 @@ async function findPluginRecord(name) {
|
|
|
219
513
|
const registry = await readPluginRegistry();
|
|
220
514
|
return registry.plugins.find((p) => p.name === name);
|
|
221
515
|
}
|
|
222
|
-
var
|
|
516
|
+
var import_promises5;
|
|
223
517
|
var init_registry2 = __esm({
|
|
224
518
|
"src/plugins/registry.ts"() {
|
|
225
519
|
"use strict";
|
|
226
|
-
|
|
520
|
+
import_promises5 = __toESM(require("fs/promises"), 1);
|
|
227
521
|
init_paths();
|
|
228
522
|
}
|
|
229
523
|
});
|
|
@@ -250,7 +544,7 @@ var init_logger = __esm({
|
|
|
250
544
|
// src/plugins/host.ts
|
|
251
545
|
async function invokePlugin(command, op, params, opts) {
|
|
252
546
|
const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
253
|
-
const scope = opts?.name ?? (0,
|
|
547
|
+
const scope = opts?.name ?? (0, import_node_path4.basename)(command[1] ?? command[0] ?? "plugin");
|
|
254
548
|
return new Promise((resolve, reject) => {
|
|
255
549
|
const child = (0, import_node_child_process.spawn)(command[0], command.slice(1), {
|
|
256
550
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -354,7 +648,7 @@ function mapHits(result, collections) {
|
|
|
354
648
|
if (typeof h.collection !== "string" || typeof h.relPath !== "string") continue;
|
|
355
649
|
const title = typeof h.title === "string" ? h.title : h.relPath;
|
|
356
650
|
const coll = collections.find((c) => c.name === h.collection);
|
|
357
|
-
const abs = coll ?
|
|
651
|
+
const abs = coll ? import_node_path4.default.join(coll.path, h.relPath) : h.relPath;
|
|
358
652
|
out.push({
|
|
359
653
|
collection: h.collection,
|
|
360
654
|
path: abs,
|
|
@@ -455,12 +749,12 @@ async function listSearchModes() {
|
|
|
455
749
|
}
|
|
456
750
|
return modes;
|
|
457
751
|
}
|
|
458
|
-
var import_node_child_process,
|
|
752
|
+
var import_node_child_process, import_node_path4, LEVEL_METHOD, DEFAULT_TIMEOUT_MS, SEARCH_TIMEOUT_MS, LONG_TIMEOUT_MS;
|
|
459
753
|
var init_host = __esm({
|
|
460
754
|
"src/plugins/host.ts"() {
|
|
461
755
|
"use strict";
|
|
462
756
|
import_node_child_process = require("child_process");
|
|
463
|
-
|
|
757
|
+
import_node_path4 = __toESM(require("path"), 1);
|
|
464
758
|
init_logger();
|
|
465
759
|
init_types();
|
|
466
760
|
init_registry2();
|
|
@@ -534,7 +828,7 @@ async function findCollectionById(id) {
|
|
|
534
828
|
async function createCollection(name, opts) {
|
|
535
829
|
if (await findCollection(name)) throw new CollectionExistsError(name);
|
|
536
830
|
const dir = collectionDir(name);
|
|
537
|
-
await
|
|
831
|
+
await import_promises6.default.mkdir(dir, { recursive: true });
|
|
538
832
|
const entry = {
|
|
539
833
|
name,
|
|
540
834
|
type: "global",
|
|
@@ -551,16 +845,16 @@ async function removeCollection(name) {
|
|
|
551
845
|
if (!entry) throw new CollectionNotFoundError(name);
|
|
552
846
|
await unregisterCollection(name);
|
|
553
847
|
if (entry.type === "global") {
|
|
554
|
-
await
|
|
848
|
+
await import_promises6.default.rm(entry.path, { recursive: true, force: true });
|
|
555
849
|
}
|
|
556
850
|
void fireHook("collection-removed", name);
|
|
557
851
|
return entry;
|
|
558
852
|
}
|
|
559
|
-
var
|
|
853
|
+
var import_promises6;
|
|
560
854
|
var init_collections = __esm({
|
|
561
855
|
"src/collections.ts"() {
|
|
562
856
|
"use strict";
|
|
563
|
-
|
|
857
|
+
import_promises6 = __toESM(require("fs/promises"), 1);
|
|
564
858
|
init_paths();
|
|
565
859
|
init_registry();
|
|
566
860
|
init_errors();
|
|
@@ -577,6 +871,7 @@ __export(index_exports, {
|
|
|
577
871
|
DiffwikiError: () => DiffwikiError,
|
|
578
872
|
InvalidExportSelectionError: () => InvalidExportSelectionError,
|
|
579
873
|
InvalidTargetError: () => InvalidTargetError,
|
|
874
|
+
META_KINDS: () => META_KINDS,
|
|
580
875
|
NATIVE_ENGINE: () => NATIVE_ENGINE,
|
|
581
876
|
NoDefaultCollectionError: () => NoDefaultCollectionError,
|
|
582
877
|
PROJECT_CONFIG_FILE: () => PROJECT_CONFIG_FILE,
|
|
@@ -598,10 +893,13 @@ __export(index_exports, {
|
|
|
598
893
|
configPath: () => configPath,
|
|
599
894
|
createArticle: () => createArticle,
|
|
600
895
|
createCollection: () => createCollection,
|
|
896
|
+
createMetaRecord: () => createMetaRecord,
|
|
601
897
|
currentBranch: () => currentBranch,
|
|
602
898
|
deregisterPlugin: () => deregisterPlugin,
|
|
603
899
|
doctor: () => doctor,
|
|
604
900
|
emitPluginEvent: () => emitPluginEvent,
|
|
901
|
+
ensureArticleMeta: () => ensureArticleMeta,
|
|
902
|
+
ensureCollectionMeta: () => ensureCollectionMeta,
|
|
605
903
|
ensureGitRepo: () => ensureGitRepo,
|
|
606
904
|
ensureHome: () => ensureHome,
|
|
607
905
|
ensurePluginRoot: () => ensurePluginRoot,
|
|
@@ -613,6 +911,7 @@ __export(index_exports, {
|
|
|
613
911
|
getConfigValue: () => getConfigValue,
|
|
614
912
|
getLogger: () => getLogger,
|
|
615
913
|
gitToplevel: () => gitToplevel,
|
|
914
|
+
initMeta: () => initMeta,
|
|
616
915
|
initRepoWiki: () => initRepoWiki,
|
|
617
916
|
installPlugin: () => installPlugin,
|
|
618
917
|
invalidateLinkGraph: () => invalidateLinkGraph,
|
|
@@ -629,11 +928,14 @@ __export(index_exports, {
|
|
|
629
928
|
listTemplates: () => listTemplates,
|
|
630
929
|
loadSearchProvider: () => loadSearchProvider,
|
|
631
930
|
localGraph: () => localGraph,
|
|
931
|
+
metaDir: () => metaDir,
|
|
932
|
+
mirrorSavedArticle: () => mirrorSavedArticle,
|
|
632
933
|
nativeSearch: () => nativeSearch,
|
|
633
934
|
parseAddTarget: () => parseAddTarget,
|
|
634
935
|
parseArticle: () => parseArticle,
|
|
635
936
|
parseFrontmatter: () => parseFrontmatter,
|
|
636
937
|
parsePathTarget: () => parsePathTarget,
|
|
938
|
+
patchArticle: () => patchArticle,
|
|
637
939
|
pluginsDir: () => pluginsDir,
|
|
638
940
|
pluginsRegistryPath: () => pluginsRegistryPath,
|
|
639
941
|
query: () => query,
|
|
@@ -649,6 +951,7 @@ __export(index_exports, {
|
|
|
649
951
|
registerCollection: () => registerCollection,
|
|
650
952
|
registerPlugin: () => registerPlugin,
|
|
651
953
|
registryPath: () => registryPath,
|
|
954
|
+
reindexMeta: () => reindexMeta,
|
|
652
955
|
removeArticle: () => removeArticle,
|
|
653
956
|
removeCollection: () => removeCollection,
|
|
654
957
|
removePlugin: () => removePlugin,
|
|
@@ -682,9 +985,11 @@ __export(index_exports, {
|
|
|
682
985
|
toTreeNodes: () => toTreeNodes,
|
|
683
986
|
tokenize: () => tokenize,
|
|
684
987
|
unregisterCollection: () => unregisterCollection,
|
|
988
|
+
updateArticle: () => updateArticle,
|
|
685
989
|
updateArticleBody: () => updateArticleBody,
|
|
686
990
|
upsertPlugin: () => upsertPlugin,
|
|
687
991
|
upstreamStatus: () => upstreamStatus,
|
|
992
|
+
validateMetaArticlePath: () => validateMetaArticlePath,
|
|
688
993
|
writeConfig: () => writeConfig,
|
|
689
994
|
writePluginRegistry: () => writePluginRegistry,
|
|
690
995
|
writeRegistry: () => writeRegistry
|
|
@@ -724,9 +1029,10 @@ init_paths();
|
|
|
724
1029
|
// src/frontmatter.ts
|
|
725
1030
|
var import_gray_matter = __toESM(require("gray-matter"), 1);
|
|
726
1031
|
var import_yaml = require("yaml");
|
|
1032
|
+
init_errors();
|
|
727
1033
|
var YAML_ENGINE = {
|
|
728
1034
|
parse: (input) => {
|
|
729
|
-
const data = (0, import_yaml.parse)(input);
|
|
1035
|
+
const data = (0, import_yaml.parse)(input.replace(/\r\n?/g, "\n"));
|
|
730
1036
|
return data && typeof data === "object" ? data : {};
|
|
731
1037
|
},
|
|
732
1038
|
stringify: (data) => (0, import_yaml.stringify)(data)
|
|
@@ -770,6 +1076,31 @@ var FRONTMATTER_BLOCK_RE = /^\uFEFF?[ \t]*---\r?\n[\s\S]*?\r?\n---[ \t]*(?:\r?\n
|
|
|
770
1076
|
function stripFrontmatter(raw) {
|
|
771
1077
|
return raw.replace(FRONTMATTER_BLOCK_RE, "");
|
|
772
1078
|
}
|
|
1079
|
+
function patchArticle(raw, fields, body) {
|
|
1080
|
+
const fence = /^(\uFEFF?[ \t]*---[ \t]*\r?\n)([\s\S]*?)(\r?\n---[ \t]*(?:\r?\n|$))/.exec(raw);
|
|
1081
|
+
if (Object.keys(fields).length === 0) return fence ? fence[0] + (body ?? raw.slice(fence[0].length)) : body ?? raw;
|
|
1082
|
+
const originalYaml = fence?.[2] ?? "";
|
|
1083
|
+
const doc = (0, import_yaml.parseDocument)(originalYaml);
|
|
1084
|
+
if (doc.errors.length)
|
|
1085
|
+
throw new DiffwikiError("Cannot update article with invalid YAML frontmatter. Fix its header first.");
|
|
1086
|
+
const titleNode = doc.get("title", true);
|
|
1087
|
+
if (fence && body === void 0 && Object.keys(fields).length === 1 && typeof fields.title === "string" && !/[\r\n]/.test(fields.title) && (0, import_yaml.isScalar)(titleNode) && titleNode.range) {
|
|
1088
|
+
const [start, end] = titleNode.range;
|
|
1089
|
+
if (!/[\r\n]/.test(originalYaml.slice(start, end))) {
|
|
1090
|
+
const scalar = (0, import_yaml.stringify)(fields.title, { lineWidth: 0 }).trimEnd();
|
|
1091
|
+
return fence[1] + originalYaml.slice(0, start) + scalar + originalYaml.slice(end) + fence[3] + raw.slice(fence[0].length);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
1095
|
+
if (value === void 0) doc.delete(key);
|
|
1096
|
+
else doc.set(key, value);
|
|
1097
|
+
}
|
|
1098
|
+
const newline = fence?.[1].endsWith("\r\n") ? "\r\n" : "\n";
|
|
1099
|
+
const yaml = doc.toString({ lineWidth: 0 }).replace(/\n$/, "").replace(/\r?\n/g, newline);
|
|
1100
|
+
const content = body ?? (fence ? raw.slice(fence[0].length) : raw);
|
|
1101
|
+
const close = fence?.[3] ?? `${newline}---${newline}`;
|
|
1102
|
+
return (fence?.[1] ?? `---${newline}`) + yaml + close + (body && !close.endsWith("\n") ? newline : "") + content;
|
|
1103
|
+
}
|
|
773
1104
|
function parseFrontmatter(raw) {
|
|
774
1105
|
try {
|
|
775
1106
|
const { data, content } = (0, import_gray_matter.default)(raw, MATTER_OPTS);
|
|
@@ -819,28 +1150,40 @@ async function seedTemplates(srcDir) {
|
|
|
819
1150
|
return { seeded, skipped, dir };
|
|
820
1151
|
}
|
|
821
1152
|
async function listTemplateShapes(srcDir) {
|
|
822
|
-
|
|
823
|
-
return files.filter((f) => f.endsWith("-template.md")).map((f) => f.replace(/-template\.md$/, "")).sort();
|
|
1153
|
+
return (await listTemplates(srcDir)).map((info) => info.shape).sort();
|
|
824
1154
|
}
|
|
825
|
-
async function listTemplates(srcDir) {
|
|
826
|
-
const files =
|
|
1155
|
+
async function listTemplates(srcDir, category = "article") {
|
|
1156
|
+
const files = /* @__PURE__ */ new Map();
|
|
1157
|
+
for (const dir of [srcDir, templatesDir()]) {
|
|
1158
|
+
let names;
|
|
1159
|
+
try {
|
|
1160
|
+
names = await import_promises2.default.readdir(dir);
|
|
1161
|
+
} catch (err) {
|
|
1162
|
+
if (err.code === "ENOENT") continue;
|
|
1163
|
+
throw err;
|
|
1164
|
+
}
|
|
1165
|
+
for (const name of names.filter((name2) => name2.endsWith("-template.md")).sort())
|
|
1166
|
+
files.set(name, import_node_path2.default.resolve(dir, name));
|
|
1167
|
+
}
|
|
827
1168
|
const out = [];
|
|
828
|
-
for (const file of files) {
|
|
1169
|
+
for (const [file, filePath] of [...files].sort(([a], [b]) => a.localeCompare(b))) {
|
|
829
1170
|
let data = {};
|
|
830
1171
|
try {
|
|
831
|
-
({ data } = parseFrontmatter(await import_promises2.default.readFile(
|
|
1172
|
+
({ data } = parseFrontmatter(await import_promises2.default.readFile(filePath, "utf8")));
|
|
832
1173
|
} catch {
|
|
833
1174
|
}
|
|
834
1175
|
const shape = typeof data.shape === "string" && data.shape ? data.shape : file.replace(/-template\.md$/, "");
|
|
835
1176
|
const purpose = typeof data.purpose === "string" ? data.purpose : "";
|
|
836
1177
|
const budget = Array.isArray(data.budget) && data.budget.length === 2 ? [Number(data.budget[0]), Number(data.budget[1])] : void 0;
|
|
837
|
-
|
|
1178
|
+
const templateCategory = data.category === "meta" ? "meta" : "article";
|
|
1179
|
+
if (category === "all" || category === templateCategory)
|
|
1180
|
+
out.push({ shape, purpose, budget, file, path: filePath, category: templateCategory });
|
|
838
1181
|
}
|
|
839
1182
|
return out;
|
|
840
1183
|
}
|
|
841
1184
|
|
|
842
1185
|
// src/config.ts
|
|
843
|
-
var
|
|
1186
|
+
var import_promises7 = __toESM(require("fs/promises"), 1);
|
|
844
1187
|
init_paths();
|
|
845
1188
|
init_collections();
|
|
846
1189
|
init_errors();
|
|
@@ -851,15 +1194,15 @@ function isMissing3(err) {
|
|
|
851
1194
|
}
|
|
852
1195
|
async function readConfig() {
|
|
853
1196
|
try {
|
|
854
|
-
return JSON.parse(await
|
|
1197
|
+
return JSON.parse(await import_promises7.default.readFile(configPath(), "utf8"));
|
|
855
1198
|
} catch (err) {
|
|
856
1199
|
if (isMissing3(err)) return {};
|
|
857
1200
|
throw err;
|
|
858
1201
|
}
|
|
859
1202
|
}
|
|
860
1203
|
async function writeConfig(config) {
|
|
861
|
-
await
|
|
862
|
-
await
|
|
1204
|
+
await import_promises7.default.mkdir(resolveHome(), { recursive: true });
|
|
1205
|
+
await import_promises7.default.writeFile(configPath(), `${JSON.stringify(config, null, 2)}
|
|
863
1206
|
`, "utf8");
|
|
864
1207
|
}
|
|
865
1208
|
async function setConfigValue(key, value) {
|
|
@@ -892,6 +1235,7 @@ async function showSystemEnabled() {
|
|
|
892
1235
|
}
|
|
893
1236
|
|
|
894
1237
|
// src/index.ts
|
|
1238
|
+
init_meta();
|
|
895
1239
|
init_registry();
|
|
896
1240
|
|
|
897
1241
|
// src/slugify.ts
|
|
@@ -908,9 +1252,10 @@ function slugifyPath(input) {
|
|
|
908
1252
|
init_collections();
|
|
909
1253
|
|
|
910
1254
|
// src/articles.ts
|
|
911
|
-
var
|
|
912
|
-
var
|
|
1255
|
+
var import_promises8 = __toESM(require("fs/promises"), 1);
|
|
1256
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
913
1257
|
init_collections();
|
|
1258
|
+
init_meta();
|
|
914
1259
|
init_errors();
|
|
915
1260
|
async function fireHook2(event, collection, relPath) {
|
|
916
1261
|
try {
|
|
@@ -956,63 +1301,53 @@ function ensureMdPath(file) {
|
|
|
956
1301
|
async function createArticle(opts) {
|
|
957
1302
|
const { collection, title } = parseAddTarget(opts.target);
|
|
958
1303
|
const { name, dir } = await resolveCollectionDir(collection);
|
|
959
|
-
const filePath =
|
|
1304
|
+
const filePath = import_node_path5.default.join(dir, `${slugifyPath(title)}.md`);
|
|
960
1305
|
const body = opts.body ?? `# ${title}
|
|
961
1306
|
`;
|
|
962
|
-
await
|
|
963
|
-
await
|
|
1307
|
+
await import_promises8.default.mkdir(import_node_path5.default.dirname(filePath), { recursive: true });
|
|
1308
|
+
await import_promises8.default.writeFile(
|
|
964
1309
|
filePath,
|
|
965
1310
|
serializeArticle({ title, tags: opts.tags, body, audience: opts.audience, status: opts.status }),
|
|
966
1311
|
"utf8"
|
|
967
1312
|
);
|
|
968
|
-
void fireHook2("article-created", name,
|
|
1313
|
+
void fireHook2("article-created", name, import_node_path5.default.relative(dir, filePath));
|
|
1314
|
+
await mirrorSavedArticle({ name, path: dir }, filePath);
|
|
969
1315
|
return { title, tags: opts.tags, path: filePath, collection: name };
|
|
970
1316
|
}
|
|
971
1317
|
async function loadArticle(target) {
|
|
972
1318
|
const { collection, file } = parsePathTarget(target);
|
|
973
1319
|
const { dir } = await resolveCollectionDir(collection);
|
|
974
|
-
const filePath =
|
|
1320
|
+
const filePath = import_node_path5.default.join(dir, validateMetaArticlePath(ensureMdPath(file)));
|
|
975
1321
|
try {
|
|
976
|
-
return { filePath, dir, raw: await
|
|
1322
|
+
return { filePath, dir, raw: await import_promises8.default.readFile(filePath, "utf8"), collection };
|
|
977
1323
|
} catch (err) {
|
|
978
1324
|
if (err.code === "ENOENT") throw new ArticleNotFoundError(target);
|
|
979
1325
|
throw err;
|
|
980
1326
|
}
|
|
981
1327
|
}
|
|
982
|
-
async function
|
|
1328
|
+
async function updateArticle(target, updates) {
|
|
983
1329
|
const { filePath, dir, raw, collection } = await loadArticle(target);
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
);
|
|
996
|
-
void fireHook2("article-updated", collection, import_node_path4.default.relative(dir, filePath));
|
|
1330
|
+
if (updates.title !== void 0 && !updates.title.trim())
|
|
1331
|
+
throw new InvalidTargetError(updates.title, "title cannot be empty");
|
|
1332
|
+
const fields = {};
|
|
1333
|
+
if (updates.title !== void 0) fields.title = updates.title;
|
|
1334
|
+
if (updates.tags !== void 0) fields.tags = Array.from(new Set(updates.tags));
|
|
1335
|
+
if (updates.audience !== void 0) fields.audience = updates.audience === "public" ? void 0 : updates.audience;
|
|
1336
|
+
if (updates.status !== void 0) fields.status = updates.status === "published" ? void 0 : updates.status;
|
|
1337
|
+
const next = patchArticle(raw, fields, updates.body);
|
|
1338
|
+
await import_promises8.default.writeFile(filePath, next, "utf8");
|
|
1339
|
+
const parsed = parseArticle(next);
|
|
1340
|
+
void fireHook2("article-updated", collection, import_node_path5.default.relative(dir, filePath));
|
|
1341
|
+
await mirrorSavedArticle({ name: collection, path: dir }, filePath);
|
|
997
1342
|
return { title: parsed.title, tags: parsed.tags, path: filePath, collection };
|
|
998
1343
|
}
|
|
1344
|
+
function updateArticleBody(target, body) {
|
|
1345
|
+
return updateArticle(target, { body });
|
|
1346
|
+
}
|
|
999
1347
|
async function mutateTags(target, fn) {
|
|
1000
|
-
const {
|
|
1348
|
+
const { raw } = await loadArticle(target);
|
|
1001
1349
|
const parsed = parseArticle(raw);
|
|
1002
|
-
|
|
1003
|
-
await import_promises7.default.writeFile(
|
|
1004
|
-
filePath,
|
|
1005
|
-
serializeArticle({
|
|
1006
|
-
title: parsed.title,
|
|
1007
|
-
tags,
|
|
1008
|
-
audience: parsed.audience,
|
|
1009
|
-
status: parsed.status,
|
|
1010
|
-
body: parsed.body
|
|
1011
|
-
}),
|
|
1012
|
-
"utf8"
|
|
1013
|
-
);
|
|
1014
|
-
void fireHook2("article-updated", collection, import_node_path4.default.relative(dir, filePath));
|
|
1015
|
-
return { title: parsed.title, tags, path: filePath, collection };
|
|
1350
|
+
return updateArticle(target, { tags: fn(parsed.tags) });
|
|
1016
1351
|
}
|
|
1017
1352
|
function addTags(target, tags) {
|
|
1018
1353
|
return mutateTags(target, (current) => Array.from(/* @__PURE__ */ new Set([...current, ...tags])));
|
|
@@ -1025,37 +1360,27 @@ function setTags(target, tags) {
|
|
|
1025
1360
|
return mutateTags(target, () => Array.from(new Set(tags)));
|
|
1026
1361
|
}
|
|
1027
1362
|
async function setArticleMeta(target, meta) {
|
|
1028
|
-
|
|
1029
|
-
const parsed = parseArticle(raw);
|
|
1030
|
-
const audience = meta.audience ?? parsed.audience;
|
|
1031
|
-
const status = meta.status ?? parsed.status;
|
|
1032
|
-
await import_promises7.default.writeFile(
|
|
1033
|
-
filePath,
|
|
1034
|
-
serializeArticle({ title: parsed.title, tags: parsed.tags, audience, status, body: parsed.body }),
|
|
1035
|
-
"utf8"
|
|
1036
|
-
);
|
|
1037
|
-
void fireHook2("article-updated", collection, import_node_path4.default.relative(dir, filePath));
|
|
1038
|
-
return { title: parsed.title, tags: parsed.tags, path: filePath, collection };
|
|
1363
|
+
return updateArticle(target, meta);
|
|
1039
1364
|
}
|
|
1040
1365
|
async function removeArticle(target) {
|
|
1041
1366
|
const { filePath, dir, collection } = await loadArticle(target);
|
|
1042
|
-
await
|
|
1043
|
-
void fireHook2("article-removed", collection,
|
|
1367
|
+
await import_promises8.default.rm(filePath);
|
|
1368
|
+
void fireHook2("article-removed", collection, import_node_path5.default.relative(dir, filePath));
|
|
1044
1369
|
return filePath;
|
|
1045
1370
|
}
|
|
1046
1371
|
|
|
1047
1372
|
// src/repo.ts
|
|
1048
|
-
var
|
|
1049
|
-
var
|
|
1373
|
+
var import_promises10 = __toESM(require("fs/promises"), 1);
|
|
1374
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
1050
1375
|
var import_node_child_process3 = require("child_process");
|
|
1051
1376
|
var import_node_util2 = require("util");
|
|
1052
|
-
var
|
|
1377
|
+
var import_yaml3 = require("yaml");
|
|
1053
1378
|
init_collections();
|
|
1054
1379
|
init_registry();
|
|
1055
1380
|
|
|
1056
1381
|
// src/git.ts
|
|
1057
|
-
var
|
|
1058
|
-
var
|
|
1382
|
+
var import_promises9 = __toESM(require("fs/promises"), 1);
|
|
1383
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
1059
1384
|
var import_node_child_process2 = require("child_process");
|
|
1060
1385
|
var import_node_util = require("util");
|
|
1061
1386
|
init_logger();
|
|
@@ -1081,8 +1406,8 @@ async function isLinkedWorktree(cwd) {
|
|
|
1081
1406
|
const gitDir = await git(cwd, ["rev-parse", "--absolute-git-dir"]);
|
|
1082
1407
|
const commonDir = await git(cwd, ["rev-parse", "--git-common-dir"]);
|
|
1083
1408
|
if (!gitDir || !commonDir) return false;
|
|
1084
|
-
const commonAbs =
|
|
1085
|
-
return
|
|
1409
|
+
const commonAbs = import_node_path6.default.isAbsolute(commonDir) ? commonDir : import_node_path6.default.resolve(cwd, commonDir);
|
|
1410
|
+
return import_node_path6.default.resolve(gitDir) !== import_node_path6.default.resolve(commonAbs);
|
|
1086
1411
|
}
|
|
1087
1412
|
async function repoRemoteName(cwd) {
|
|
1088
1413
|
const url = await git(cwd, ["remote", "get-url", "origin"]);
|
|
@@ -1124,8 +1449,8 @@ var timestampCache = /* @__PURE__ */ new Map();
|
|
|
1124
1449
|
async function fileTimestamps(absPath) {
|
|
1125
1450
|
const cached2 = timestampCache.get(absPath);
|
|
1126
1451
|
if (cached2) return cached2;
|
|
1127
|
-
const dir =
|
|
1128
|
-
const base =
|
|
1452
|
+
const dir = import_node_path6.default.dirname(absPath);
|
|
1453
|
+
const base = import_node_path6.default.basename(absPath);
|
|
1129
1454
|
const [addLog, lastLog] = await Promise.all([
|
|
1130
1455
|
git(dir, ["log", "--follow", "--diff-filter=A", "--format=%aI", "--", base]),
|
|
1131
1456
|
git(dir, ["log", "-1", "--format=%aI", "--", base])
|
|
@@ -1136,7 +1461,7 @@ async function fileTimestamps(absPath) {
|
|
|
1136
1461
|
let updated = gitUpdated;
|
|
1137
1462
|
if (!created || !updated) {
|
|
1138
1463
|
try {
|
|
1139
|
-
const st = await
|
|
1464
|
+
const st = await import_promises9.default.stat(absPath);
|
|
1140
1465
|
if (!updated) updated = st.mtime.toISOString();
|
|
1141
1466
|
if (!created) created = isEpochish(st.birthtime) ? st.mtime.toISOString() : st.birthtime.toISOString();
|
|
1142
1467
|
} catch {
|
|
@@ -1154,18 +1479,18 @@ var REPO_CONFIG_FILE = "diffwiki.yaml";
|
|
|
1154
1479
|
async function detectRepoName(cwd) {
|
|
1155
1480
|
try {
|
|
1156
1481
|
const { stdout } = await execFileAsync2("git", ["rev-parse", "--show-toplevel"], { cwd });
|
|
1157
|
-
return
|
|
1482
|
+
return import_node_path7.default.basename(stdout.trim());
|
|
1158
1483
|
} catch {
|
|
1159
|
-
return
|
|
1484
|
+
return import_node_path7.default.basename(import_node_path7.default.resolve(cwd));
|
|
1160
1485
|
}
|
|
1161
1486
|
}
|
|
1162
1487
|
async function readRepoConfig(cwd) {
|
|
1163
1488
|
try {
|
|
1164
|
-
const raw = await
|
|
1165
|
-
const parsed = (0,
|
|
1489
|
+
const raw = await import_promises10.default.readFile(import_node_path7.default.join(cwd, REPO_CONFIG_FILE), "utf8");
|
|
1490
|
+
const parsed = (0, import_yaml3.parse)(raw) ?? {};
|
|
1166
1491
|
if (!parsed.path) return void 0;
|
|
1167
1492
|
return {
|
|
1168
|
-
collection: parsed.collection ??
|
|
1493
|
+
collection: parsed.collection ?? import_node_path7.default.basename(import_node_path7.default.resolve(cwd)),
|
|
1169
1494
|
path: parsed.path
|
|
1170
1495
|
};
|
|
1171
1496
|
} catch (err) {
|
|
@@ -1174,9 +1499,9 @@ async function readRepoConfig(cwd) {
|
|
|
1174
1499
|
}
|
|
1175
1500
|
}
|
|
1176
1501
|
async function initRepoWiki(opts) {
|
|
1177
|
-
const cwd =
|
|
1502
|
+
const cwd = import_node_path7.default.resolve(opts.cwd);
|
|
1178
1503
|
const wikiPath = opts.wikiPath ?? "wiki";
|
|
1179
|
-
const absWiki =
|
|
1504
|
+
const absWiki = import_node_path7.default.join(cwd, wikiPath);
|
|
1180
1505
|
let name = opts.collection ?? await detectRepoName(cwd);
|
|
1181
1506
|
const existing = await findCollection(name);
|
|
1182
1507
|
if (existing && existing.path !== absWiki) {
|
|
@@ -1191,9 +1516,9 @@ async function initRepoWiki(opts) {
|
|
|
1191
1516
|
n += 1;
|
|
1192
1517
|
}
|
|
1193
1518
|
}
|
|
1194
|
-
await
|
|
1519
|
+
await import_promises10.default.mkdir(absWiki, { recursive: true });
|
|
1195
1520
|
const config = { collection: name, path: wikiPath };
|
|
1196
|
-
await
|
|
1521
|
+
await import_promises10.default.writeFile(import_node_path7.default.join(cwd, REPO_CONFIG_FILE), (0, import_yaml3.stringify)(config), "utf8");
|
|
1197
1522
|
const entry = {
|
|
1198
1523
|
name,
|
|
1199
1524
|
type: "repo",
|
|
@@ -1204,15 +1529,15 @@ async function initRepoWiki(opts) {
|
|
|
1204
1529
|
return entry;
|
|
1205
1530
|
}
|
|
1206
1531
|
async function addExternalCollection(opts) {
|
|
1207
|
-
const cwd =
|
|
1532
|
+
const cwd = import_node_path7.default.resolve(opts.cwd);
|
|
1208
1533
|
const root = await ensureGitRepo(cwd);
|
|
1209
1534
|
if (await isLinkedWorktree(cwd)) throw new WorktreeNotAllowedError(root);
|
|
1210
1535
|
const id = await repoId(root) ?? `path:${root}`;
|
|
1211
1536
|
const already = await findCollectionById(id);
|
|
1212
1537
|
if (already) throw new CollectionAlreadyAddedError(already.name);
|
|
1213
1538
|
const docsRel = opts.docsDir ?? "docs";
|
|
1214
|
-
const contentDir =
|
|
1215
|
-
let name = await repoRemoteName(root) ??
|
|
1539
|
+
const contentDir = import_node_path7.default.join(root, docsRel);
|
|
1540
|
+
let name = await repoRemoteName(root) ?? import_node_path7.default.basename(root);
|
|
1216
1541
|
if (await findCollection(name)) {
|
|
1217
1542
|
let n = 2;
|
|
1218
1543
|
while (await findCollection(`${name}-${n}`)) n += 1;
|
|
@@ -1231,21 +1556,21 @@ async function addExternalCollection(opts) {
|
|
|
1231
1556
|
return entry;
|
|
1232
1557
|
}
|
|
1233
1558
|
async function resolveCwdCollection(cwd) {
|
|
1234
|
-
const abs =
|
|
1559
|
+
const abs = import_node_path7.default.resolve(cwd);
|
|
1235
1560
|
const root = await gitToplevel(abs);
|
|
1236
1561
|
if (root) {
|
|
1237
1562
|
const id = await repoId(root) ?? `path:${root}`;
|
|
1238
1563
|
const byId = await findCollectionById(id);
|
|
1239
1564
|
if (byId) return byId;
|
|
1240
1565
|
}
|
|
1241
|
-
const
|
|
1566
|
+
const within2 = (base) => abs === base || abs.startsWith(base + import_node_path7.default.sep);
|
|
1242
1567
|
const all = await listCollections();
|
|
1243
1568
|
let best;
|
|
1244
1569
|
let bestLen = -1;
|
|
1245
1570
|
for (const c of all) {
|
|
1246
1571
|
const bases = [c.path, c.repoRoot].filter((b) => b !== void 0);
|
|
1247
1572
|
for (const b of bases) {
|
|
1248
|
-
if (
|
|
1573
|
+
if (within2(b) && b.length > bestLen) {
|
|
1249
1574
|
best = c;
|
|
1250
1575
|
bestLen = b.length;
|
|
1251
1576
|
}
|
|
@@ -1262,10 +1587,10 @@ async function collectionGitStatus(entry) {
|
|
|
1262
1587
|
}
|
|
1263
1588
|
|
|
1264
1589
|
// src/project.ts
|
|
1265
|
-
var
|
|
1590
|
+
var import_promises11 = __toESM(require("fs/promises"), 1);
|
|
1266
1591
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
1267
|
-
var
|
|
1268
|
-
var
|
|
1592
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
1593
|
+
var import_yaml4 = require("yaml");
|
|
1269
1594
|
var PROJECT_CONFIG_FILE = "diffwiki.yaml";
|
|
1270
1595
|
function normaliseSite(raw) {
|
|
1271
1596
|
if (!raw || typeof raw !== "object") return void 0;
|
|
@@ -1319,12 +1644,12 @@ function normaliseContext(raw) {
|
|
|
1319
1644
|
async function readProjectConfig(dir) {
|
|
1320
1645
|
let raw;
|
|
1321
1646
|
try {
|
|
1322
|
-
raw = await
|
|
1647
|
+
raw = await import_promises11.default.readFile(import_node_path8.default.join(dir, PROJECT_CONFIG_FILE), "utf8");
|
|
1323
1648
|
} catch (err) {
|
|
1324
1649
|
if (err.code === "ENOENT") return void 0;
|
|
1325
1650
|
throw err;
|
|
1326
1651
|
}
|
|
1327
|
-
const parsed = (0,
|
|
1652
|
+
const parsed = (0, import_yaml4.parse)(raw) ?? {};
|
|
1328
1653
|
const collections = [];
|
|
1329
1654
|
if (Array.isArray(parsed.collections)) {
|
|
1330
1655
|
for (const c of parsed.collections) {
|
|
@@ -1340,7 +1665,7 @@ async function readProjectConfig(dir) {
|
|
|
1340
1665
|
}
|
|
1341
1666
|
collections.sort((a, b) => (a.order ?? Number.MAX_SAFE_INTEGER) - (b.order ?? Number.MAX_SAFE_INTEGER));
|
|
1342
1667
|
} else if (typeof parsed.path === "string") {
|
|
1343
|
-
const name = typeof parsed.collection === "string" ? parsed.collection :
|
|
1668
|
+
const name = typeof parsed.collection === "string" ? parsed.collection : import_node_path8.default.basename(import_node_path8.default.resolve(dir));
|
|
1344
1669
|
collections.push({ name, path: parsed.path });
|
|
1345
1670
|
}
|
|
1346
1671
|
const site = normaliseSite(parsed.site);
|
|
@@ -1351,27 +1676,27 @@ async function readContextCollections(dir) {
|
|
|
1351
1676
|
return (await readProjectConfig(dir))?.context?.collections ?? [];
|
|
1352
1677
|
}
|
|
1353
1678
|
async function setContextCollections(dir, collections) {
|
|
1354
|
-
const file =
|
|
1679
|
+
const file = import_node_path8.default.join(dir, PROJECT_CONFIG_FILE);
|
|
1355
1680
|
let raw = "";
|
|
1356
1681
|
try {
|
|
1357
|
-
raw = await
|
|
1682
|
+
raw = await import_promises11.default.readFile(file, "utf8");
|
|
1358
1683
|
} catch (err) {
|
|
1359
1684
|
if (err.code !== "ENOENT") throw err;
|
|
1360
1685
|
}
|
|
1361
|
-
const doc = (0,
|
|
1686
|
+
const doc = (0, import_yaml4.parseDocument)(raw);
|
|
1362
1687
|
doc.setIn(["context", "collections"], collections);
|
|
1363
|
-
await
|
|
1688
|
+
await import_promises11.default.writeFile(file, doc.toString(), "utf8");
|
|
1364
1689
|
return file;
|
|
1365
1690
|
}
|
|
1366
1691
|
async function resolveProjectCollections(dir) {
|
|
1367
1692
|
const config = await readProjectConfig(dir);
|
|
1368
1693
|
if (!config) return [];
|
|
1369
|
-
const root =
|
|
1694
|
+
const root = import_node_path8.default.resolve(dir);
|
|
1370
1695
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1371
1696
|
return config.collections.map((c) => ({
|
|
1372
1697
|
name: c.name,
|
|
1373
1698
|
type: "repo",
|
|
1374
|
-
path:
|
|
1699
|
+
path: import_node_path8.default.resolve(root, c.path),
|
|
1375
1700
|
createdAt
|
|
1376
1701
|
}));
|
|
1377
1702
|
}
|
|
@@ -1381,11 +1706,11 @@ async function readSiteConfig(dir) {
|
|
|
1381
1706
|
async function seedProjectHome(dir) {
|
|
1382
1707
|
const entries = await resolveProjectCollections(dir);
|
|
1383
1708
|
if (entries.length === 0) {
|
|
1384
|
-
throw new Error(`No collections to render from diffwiki.yaml in ${
|
|
1709
|
+
throw new Error(`No collections to render from diffwiki.yaml in ${import_node_path8.default.resolve(dir)}`);
|
|
1385
1710
|
}
|
|
1386
|
-
const home = await
|
|
1387
|
-
await
|
|
1388
|
-
|
|
1711
|
+
const home = await import_promises11.default.mkdtemp(import_node_path8.default.join(import_node_os2.default.tmpdir(), "diffwiki-project-"));
|
|
1712
|
+
await import_promises11.default.writeFile(
|
|
1713
|
+
import_node_path8.default.join(home, "registry.json"),
|
|
1389
1714
|
`${JSON.stringify({ version: 1, collections: entries }, null, 2)}
|
|
1390
1715
|
`,
|
|
1391
1716
|
"utf8"
|
|
@@ -1397,8 +1722,8 @@ async function seedProjectHome(dir) {
|
|
|
1397
1722
|
init_collections();
|
|
1398
1723
|
|
|
1399
1724
|
// src/browse.ts
|
|
1400
|
-
var
|
|
1401
|
-
var
|
|
1725
|
+
var import_promises12 = __toESM(require("fs/promises"), 1);
|
|
1726
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
1402
1727
|
init_collections();
|
|
1403
1728
|
init_errors();
|
|
1404
1729
|
var INDEX_RE = /^(index|readme)\.mdx?$/i;
|
|
@@ -1444,7 +1769,7 @@ function buildArticleTree(relPaths) {
|
|
|
1444
1769
|
async function collectRelPaths(dir, rel = "") {
|
|
1445
1770
|
let dirents;
|
|
1446
1771
|
try {
|
|
1447
|
-
dirents = await
|
|
1772
|
+
dirents = await import_promises12.default.readdir(dir, { withFileTypes: true });
|
|
1448
1773
|
} catch {
|
|
1449
1774
|
return [];
|
|
1450
1775
|
}
|
|
@@ -1453,7 +1778,7 @@ async function collectRelPaths(dir, rel = "") {
|
|
|
1453
1778
|
if (d.name.startsWith(".")) continue;
|
|
1454
1779
|
const childRel = rel ? `${rel}/${d.name}` : d.name;
|
|
1455
1780
|
if (d.isDirectory()) {
|
|
1456
|
-
out.push(...await collectRelPaths(
|
|
1781
|
+
out.push(...await collectRelPaths(import_node_path9.default.join(dir, d.name), childRel));
|
|
1457
1782
|
} else if (/\.(mdx?)$/.test(d.name)) {
|
|
1458
1783
|
out.push(childRel);
|
|
1459
1784
|
}
|
|
@@ -1462,7 +1787,7 @@ async function collectRelPaths(dir, rel = "") {
|
|
|
1462
1787
|
}
|
|
1463
1788
|
async function readdirSafe(dir) {
|
|
1464
1789
|
try {
|
|
1465
|
-
return await
|
|
1790
|
+
return await import_promises12.default.readdir(dir);
|
|
1466
1791
|
} catch {
|
|
1467
1792
|
return [];
|
|
1468
1793
|
}
|
|
@@ -1480,7 +1805,7 @@ var CHEAP_META_BYTES = 8192;
|
|
|
1480
1805
|
async function cheapMeta(absPath) {
|
|
1481
1806
|
let handle;
|
|
1482
1807
|
try {
|
|
1483
|
-
handle = await
|
|
1808
|
+
handle = await import_promises12.default.open(absPath, "r");
|
|
1484
1809
|
const buf = Buffer.alloc(CHEAP_META_BYTES);
|
|
1485
1810
|
const { bytesRead } = await handle.read(buf, 0, CHEAP_META_BYTES, 0);
|
|
1486
1811
|
const snippet = buf.subarray(0, bytesRead).toString("utf8");
|
|
@@ -1514,10 +1839,10 @@ async function enrichTitles(nodes, collDir) {
|
|
|
1514
1839
|
let status = node.status;
|
|
1515
1840
|
let order = node.order;
|
|
1516
1841
|
if (node.slug) {
|
|
1517
|
-
const dir =
|
|
1842
|
+
const dir = import_node_path9.default.join(collDir, node.slug);
|
|
1518
1843
|
const idx = pickIndexFile(await readdirSafe(dir));
|
|
1519
1844
|
if (idx) {
|
|
1520
|
-
const meta = await cheapMeta(
|
|
1845
|
+
const meta = await cheapMeta(import_node_path9.default.join(dir, idx));
|
|
1521
1846
|
title = meta.title ?? title;
|
|
1522
1847
|
audience = meta.audience ?? audience;
|
|
1523
1848
|
status = meta.status ?? status;
|
|
@@ -1527,7 +1852,7 @@ async function enrichTitles(nodes, collDir) {
|
|
|
1527
1852
|
return { ...node, title, audience, status, order, children };
|
|
1528
1853
|
}
|
|
1529
1854
|
for (const ext of [".mdx", ".md"]) {
|
|
1530
|
-
const meta = await cheapMeta(
|
|
1855
|
+
const meta = await cheapMeta(import_node_path9.default.join(collDir, `${node.slug}${ext}`));
|
|
1531
1856
|
if (meta.title || meta.audience || meta.status || meta.order != null) {
|
|
1532
1857
|
return {
|
|
1533
1858
|
...node,
|
|
@@ -1546,49 +1871,49 @@ async function enrichTitles(nodes, collDir) {
|
|
|
1546
1871
|
async function collectionMeta(collDir) {
|
|
1547
1872
|
const idx = pickIndexFile(await readdirSafe(collDir));
|
|
1548
1873
|
if (!idx) return {};
|
|
1549
|
-
const meta = await cheapMeta(
|
|
1874
|
+
const meta = await cheapMeta(import_node_path9.default.join(collDir, idx));
|
|
1550
1875
|
return { title: meta.title, audience: meta.audience, status: meta.status };
|
|
1551
1876
|
}
|
|
1552
1877
|
async function listArticleTree(coll) {
|
|
1553
1878
|
const entry = await findCollection(coll);
|
|
1554
1879
|
if (!entry) throw new CollectionNotFoundError(coll);
|
|
1555
1880
|
const rels = await collectRelPaths(entry.path);
|
|
1556
|
-
return enrichTitles(buildArticleTree(rels),
|
|
1881
|
+
return enrichTitles(buildArticleTree(rels), import_node_path9.default.resolve(entry.path));
|
|
1557
1882
|
}
|
|
1558
1883
|
async function readArticle(coll, slug) {
|
|
1559
1884
|
const entry = await findCollection(coll);
|
|
1560
1885
|
if (!entry) throw new CollectionNotFoundError(coll);
|
|
1561
|
-
const collDir =
|
|
1886
|
+
const collDir = import_node_path9.default.resolve(entry.path);
|
|
1562
1887
|
const normalized = slug.replace(/\\/g, "/");
|
|
1563
1888
|
if (normalized.split("/").some((s) => s === "..")) {
|
|
1564
1889
|
throw new InvalidTargetError(slug, "path traversal detected");
|
|
1565
1890
|
}
|
|
1566
1891
|
const safeSlug = normalized.split("/").filter((s) => s !== "" && s !== ".").join("/");
|
|
1567
|
-
const inJail = (p) => p === collDir || p.startsWith(collDir +
|
|
1892
|
+
const inJail = (p) => p === collDir || p.startsWith(collDir + import_node_path9.default.sep);
|
|
1568
1893
|
let abs;
|
|
1569
1894
|
let isIndex = false;
|
|
1570
1895
|
for (const rel of safeSlug ? [`${safeSlug}.mdx`, `${safeSlug}.md`] : []) {
|
|
1571
|
-
const candidate =
|
|
1896
|
+
const candidate = import_node_path9.default.resolve(collDir, rel);
|
|
1572
1897
|
if (!inJail(candidate)) throw new InvalidTargetError(slug, "path traversal detected");
|
|
1573
1898
|
try {
|
|
1574
|
-
await
|
|
1899
|
+
await import_promises12.default.access(candidate);
|
|
1575
1900
|
abs = candidate;
|
|
1576
1901
|
break;
|
|
1577
1902
|
} catch {
|
|
1578
1903
|
}
|
|
1579
1904
|
}
|
|
1580
1905
|
if (!abs) {
|
|
1581
|
-
const dir =
|
|
1906
|
+
const dir = import_node_path9.default.resolve(collDir, safeSlug);
|
|
1582
1907
|
if (inJail(dir)) {
|
|
1583
1908
|
const idx = pickIndexFile(await readdirSafe(dir));
|
|
1584
1909
|
if (idx) {
|
|
1585
|
-
abs =
|
|
1910
|
+
abs = import_node_path9.default.join(dir, idx);
|
|
1586
1911
|
isIndex = true;
|
|
1587
1912
|
}
|
|
1588
1913
|
}
|
|
1589
1914
|
}
|
|
1590
1915
|
if (!abs) throw new ArticleNotFoundError(`${coll}/${safeSlug}`);
|
|
1591
|
-
const parsed = parseArticle(await
|
|
1916
|
+
const parsed = parseArticle(await import_promises12.default.readFile(abs, "utf8"));
|
|
1592
1917
|
const title = parsed.title?.trim() || (safeSlug ? safeSlug.split("/").pop() ?? safeSlug : coll);
|
|
1593
1918
|
return {
|
|
1594
1919
|
coll,
|
|
@@ -1604,7 +1929,7 @@ async function readArticle(coll, slug) {
|
|
|
1604
1929
|
}
|
|
1605
1930
|
|
|
1606
1931
|
// src/render.ts
|
|
1607
|
-
var
|
|
1932
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
1608
1933
|
var import_unified = require("unified");
|
|
1609
1934
|
var import_remark_parse = __toESM(require("remark-parse"), 1);
|
|
1610
1935
|
var import_remark_gfm = __toESM(require("remark-gfm"), 1);
|
|
@@ -1621,7 +1946,7 @@ function isNonRelativeHref(href) {
|
|
|
1621
1946
|
return /^[a-z][a-z0-9+.-]*:/i.test(href);
|
|
1622
1947
|
}
|
|
1623
1948
|
function rehypeRewriteLinks(ctx) {
|
|
1624
|
-
const slugDir = ctx.isIndex ? ctx.slug || "." :
|
|
1949
|
+
const slugDir = ctx.isIndex ? ctx.slug || "." : import_node_path10.default.posix.dirname(ctx.slug || ".");
|
|
1625
1950
|
return (tree) => {
|
|
1626
1951
|
(0, import_unist_util_visit.visit)(tree, "element", (node) => {
|
|
1627
1952
|
if (node.tagName !== "a") return;
|
|
@@ -1631,8 +1956,8 @@ function rehypeRewriteLinks(ctx) {
|
|
|
1631
1956
|
const suffix = suffixMatch ? suffixMatch[0] : "";
|
|
1632
1957
|
const pathPart = suffix ? raw.slice(0, raw.length - suffix.length) : raw;
|
|
1633
1958
|
if (pathPart === "") return;
|
|
1634
|
-
const joined =
|
|
1635
|
-
const normalized =
|
|
1959
|
+
const joined = import_node_path10.default.posix.join(slugDir === "." ? "" : slugDir, pathPart);
|
|
1960
|
+
const normalized = import_node_path10.default.posix.normalize(joined);
|
|
1636
1961
|
const toRoute = (p) => p.replace(/\.(mdx?)$/i, "").replace(/(^|\/)(index|readme)$/i, "$1").replace(/\/+$/, "").replace(/^\/+/, "");
|
|
1637
1962
|
if (normalized.startsWith("../")) {
|
|
1638
1963
|
const sibling = normalized.slice(3);
|
|
@@ -2035,8 +2360,8 @@ function resolveCollectionSelection(all, sel = {}) {
|
|
|
2035
2360
|
}
|
|
2036
2361
|
|
|
2037
2362
|
// src/query.ts
|
|
2038
|
-
var
|
|
2039
|
-
var
|
|
2363
|
+
var import_promises13 = __toESM(require("fs/promises"), 1);
|
|
2364
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
2040
2365
|
init_collections();
|
|
2041
2366
|
init_errors();
|
|
2042
2367
|
init_logger();
|
|
@@ -2111,13 +2436,13 @@ var log2 = getLogger("query");
|
|
|
2111
2436
|
async function collectMarkdownFiles(dir) {
|
|
2112
2437
|
let entries;
|
|
2113
2438
|
try {
|
|
2114
|
-
entries = await
|
|
2439
|
+
entries = await import_promises13.default.readdir(dir, { withFileTypes: true });
|
|
2115
2440
|
} catch {
|
|
2116
2441
|
return [];
|
|
2117
2442
|
}
|
|
2118
2443
|
const files = [];
|
|
2119
2444
|
for (const entry of entries) {
|
|
2120
|
-
const full =
|
|
2445
|
+
const full = import_node_path11.default.join(dir, entry.name);
|
|
2121
2446
|
if (entry.isDirectory()) {
|
|
2122
2447
|
files.push(...await collectMarkdownFiles(full));
|
|
2123
2448
|
} else if (entry.isFile() && /\.mdx?$/.test(entry.name)) {
|
|
@@ -2144,12 +2469,12 @@ async function nativeSearch(term, collections, collection, hideSystem = false) {
|
|
|
2144
2469
|
const docs = [];
|
|
2145
2470
|
for (const entry of targets) {
|
|
2146
2471
|
for (const full of await collectMarkdownFiles(entry.path)) {
|
|
2147
|
-
const fallbackTitle =
|
|
2472
|
+
const fallbackTitle = import_node_path11.default.basename(full).replace(/\.mdx?$/, "");
|
|
2148
2473
|
let title = fallbackTitle;
|
|
2149
2474
|
let tags = [];
|
|
2150
2475
|
let body = "";
|
|
2151
2476
|
try {
|
|
2152
|
-
const parsed = parseArticle(await
|
|
2477
|
+
const parsed = parseArticle(await import_promises13.default.readFile(full, "utf8"));
|
|
2153
2478
|
if (hideSystem && parsed.audience === "system") continue;
|
|
2154
2479
|
if (parsed.title) title = parsed.title;
|
|
2155
2480
|
tags = parsed.tags;
|
|
@@ -2207,7 +2532,7 @@ async function dropSystemHits(hits) {
|
|
|
2207
2532
|
const kept = [];
|
|
2208
2533
|
for (const hit of hits) {
|
|
2209
2534
|
try {
|
|
2210
|
-
if (parseArticle(await
|
|
2535
|
+
if (parseArticle(await import_promises13.default.readFile(hit.path, "utf8")).audience === "system") continue;
|
|
2211
2536
|
} catch {
|
|
2212
2537
|
}
|
|
2213
2538
|
kept.push(hit);
|
|
@@ -2255,14 +2580,14 @@ async function query(term, opts) {
|
|
|
2255
2580
|
}
|
|
2256
2581
|
|
|
2257
2582
|
// src/search.ts
|
|
2258
|
-
var
|
|
2259
|
-
var
|
|
2583
|
+
var import_promises14 = __toESM(require("fs/promises"), 1);
|
|
2584
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
2260
2585
|
init_collections();
|
|
2261
2586
|
var isStatic2 = () => process.env.DIFFWIKI_STATIC === "1";
|
|
2262
2587
|
async function readArticleParts(collDir, slug) {
|
|
2263
2588
|
for (const ext of [".mdx", ".md"]) {
|
|
2264
2589
|
try {
|
|
2265
|
-
const parsed = parseArticle(await
|
|
2590
|
+
const parsed = parseArticle(await import_promises14.default.readFile(import_node_path12.default.join(collDir, `${slug}${ext}`), "utf8"));
|
|
2266
2591
|
return { tags: parsed.tags, body: parsed.body };
|
|
2267
2592
|
} catch {
|
|
2268
2593
|
}
|
|
@@ -2298,7 +2623,7 @@ async function buildSearchIndex(collection) {
|
|
|
2298
2623
|
for (const e of entries) {
|
|
2299
2624
|
try {
|
|
2300
2625
|
const collEntry = await findCollection(e.name);
|
|
2301
|
-
const collDir =
|
|
2626
|
+
const collDir = import_node_path12.default.resolve(collEntry?.path ?? e.path);
|
|
2302
2627
|
if (collEntry?.audience === "system") continue;
|
|
2303
2628
|
if (isStatic2()) {
|
|
2304
2629
|
const cm = await collectionMeta(collDir);
|
|
@@ -2312,15 +2637,15 @@ async function buildSearchIndex(collection) {
|
|
|
2312
2637
|
}
|
|
2313
2638
|
|
|
2314
2639
|
// src/doctor.ts
|
|
2315
|
-
var
|
|
2640
|
+
var import_promises16 = __toESM(require("fs/promises"), 1);
|
|
2316
2641
|
init_paths();
|
|
2317
2642
|
init_registry();
|
|
2318
2643
|
init_collections();
|
|
2319
2644
|
|
|
2320
2645
|
// src/plugins/manager.ts
|
|
2321
|
-
var
|
|
2646
|
+
var import_promises15 = __toESM(require("fs/promises"), 1);
|
|
2322
2647
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
2323
|
-
var
|
|
2648
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
2324
2649
|
init_paths();
|
|
2325
2650
|
init_errors();
|
|
2326
2651
|
init_collections();
|
|
@@ -2331,7 +2656,7 @@ init_host();
|
|
|
2331
2656
|
var import_node_child_process4 = require("child_process");
|
|
2332
2657
|
var import_node_util3 = require("util");
|
|
2333
2658
|
var import_node_fs = require("fs");
|
|
2334
|
-
var
|
|
2659
|
+
var import_node_path13 = require("path");
|
|
2335
2660
|
var import_node_url = require("url");
|
|
2336
2661
|
var execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process4.execFile);
|
|
2337
2662
|
async function runProcess(file, args, opts) {
|
|
@@ -2347,7 +2672,7 @@ async function runProcess(file, args, opts) {
|
|
|
2347
2672
|
// src/plugins/manager.ts
|
|
2348
2673
|
async function readPackageJson(file) {
|
|
2349
2674
|
try {
|
|
2350
|
-
return JSON.parse(await
|
|
2675
|
+
return JSON.parse(await import_promises15.default.readFile(file, "utf8"));
|
|
2351
2676
|
} catch {
|
|
2352
2677
|
return null;
|
|
2353
2678
|
}
|
|
@@ -2363,7 +2688,7 @@ function binOf(manifest) {
|
|
|
2363
2688
|
async function pluginManifestDirs(nodeModules) {
|
|
2364
2689
|
let entries;
|
|
2365
2690
|
try {
|
|
2366
|
-
entries = await
|
|
2691
|
+
entries = await import_promises15.default.readdir(nodeModules);
|
|
2367
2692
|
} catch {
|
|
2368
2693
|
return [];
|
|
2369
2694
|
}
|
|
@@ -2373,44 +2698,44 @@ async function pluginManifestDirs(nodeModules) {
|
|
|
2373
2698
|
if (entry.startsWith("@")) {
|
|
2374
2699
|
let scoped;
|
|
2375
2700
|
try {
|
|
2376
|
-
scoped = await
|
|
2701
|
+
scoped = await import_promises15.default.readdir(import_node_path14.default.join(nodeModules, entry));
|
|
2377
2702
|
} catch {
|
|
2378
2703
|
continue;
|
|
2379
2704
|
}
|
|
2380
2705
|
for (const pkg of scoped) {
|
|
2381
2706
|
if (pkg.startsWith(".")) continue;
|
|
2382
|
-
dirs.push(
|
|
2707
|
+
dirs.push(import_node_path14.default.join(nodeModules, entry, pkg));
|
|
2383
2708
|
}
|
|
2384
2709
|
} else {
|
|
2385
|
-
dirs.push(
|
|
2710
|
+
dirs.push(import_node_path14.default.join(nodeModules, entry));
|
|
2386
2711
|
}
|
|
2387
2712
|
}
|
|
2388
2713
|
return dirs;
|
|
2389
2714
|
}
|
|
2390
2715
|
async function ensurePluginRoot() {
|
|
2391
2716
|
const dir = pluginsDir();
|
|
2392
|
-
await
|
|
2393
|
-
const pkgPath =
|
|
2717
|
+
await import_promises15.default.mkdir(dir, { recursive: true });
|
|
2718
|
+
const pkgPath = import_node_path14.default.join(dir, "package.json");
|
|
2394
2719
|
if (await readPackageJson(pkgPath) === null) {
|
|
2395
2720
|
const pkg = { name: "diffwiki-plugins", private: true, version: "0.0.0" };
|
|
2396
|
-
await
|
|
2721
|
+
await import_promises15.default.writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
2397
2722
|
`, "utf8");
|
|
2398
2723
|
}
|
|
2399
2724
|
return dir;
|
|
2400
2725
|
}
|
|
2401
2726
|
async function asSearchPackage(pkgDir) {
|
|
2402
|
-
const manifest = await readPackageJson(
|
|
2727
|
+
const manifest = await readPackageJson(import_node_path14.default.join(pkgDir, "package.json"));
|
|
2403
2728
|
if (!manifest?.name || manifest.diffwiki?.kind !== "search") return null;
|
|
2404
2729
|
const bin = binOf(manifest);
|
|
2405
2730
|
if (!bin) return null;
|
|
2406
|
-
return { name: manifest.name, version: manifest.version, binAbs:
|
|
2731
|
+
return { name: manifest.name, version: manifest.version, binAbs: import_node_path14.default.resolve(pkgDir, bin) };
|
|
2407
2732
|
}
|
|
2408
2733
|
async function packageNameFromSpec(spec) {
|
|
2409
2734
|
const looksLikePath = spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("~") || spec.startsWith("file:");
|
|
2410
2735
|
if (looksLikePath) {
|
|
2411
2736
|
const raw = spec.startsWith("file:") ? spec.slice("file:".length) : spec;
|
|
2412
|
-
const abs =
|
|
2413
|
-
return (await readPackageJson(
|
|
2737
|
+
const abs = import_node_path14.default.resolve(raw.replace(/^~(?=$|\/)/, import_node_os3.default.homedir()));
|
|
2738
|
+
return (await readPackageJson(import_node_path14.default.join(abs, "package.json")))?.name ?? null;
|
|
2414
2739
|
}
|
|
2415
2740
|
if (/^(git\+|git:|https?:|ssh:)/.test(spec)) return null;
|
|
2416
2741
|
if (spec.startsWith("@")) {
|
|
@@ -2421,10 +2746,10 @@ async function packageNameFromSpec(spec) {
|
|
|
2421
2746
|
return at <= 0 ? spec : spec.slice(0, at);
|
|
2422
2747
|
}
|
|
2423
2748
|
async function resolveSearchPackage(dir, name) {
|
|
2424
|
-
return asSearchPackage(
|
|
2749
|
+
return asSearchPackage(import_node_path14.default.join(dir, "node_modules", ...name.split("/")));
|
|
2425
2750
|
}
|
|
2426
2751
|
async function discoverSearchPackage(dir) {
|
|
2427
|
-
const nodeModules =
|
|
2752
|
+
const nodeModules = import_node_path14.default.join(dir, "node_modules");
|
|
2428
2753
|
for (const pkgDir of await pluginManifestDirs(nodeModules)) {
|
|
2429
2754
|
const found = await asSearchPackage(pkgDir);
|
|
2430
2755
|
if (found) return found;
|
|
@@ -2564,7 +2889,7 @@ init_host();
|
|
|
2564
2889
|
init_types();
|
|
2565
2890
|
async function exists(target) {
|
|
2566
2891
|
try {
|
|
2567
|
-
await
|
|
2892
|
+
await import_promises16.default.access(target);
|
|
2568
2893
|
return true;
|
|
2569
2894
|
} catch {
|
|
2570
2895
|
return false;
|
|
@@ -2670,8 +2995,8 @@ async function appendPluginDiagnostics(out, config) {
|
|
|
2670
2995
|
}
|
|
2671
2996
|
|
|
2672
2997
|
// src/lint.ts
|
|
2673
|
-
var
|
|
2674
|
-
var
|
|
2998
|
+
var import_promises17 = __toESM(require("fs/promises"), 1);
|
|
2999
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
2675
3000
|
init_collections();
|
|
2676
3001
|
init_errors();
|
|
2677
3002
|
var BUDGETS = {
|
|
@@ -2765,13 +3090,13 @@ function lintArticle(article) {
|
|
|
2765
3090
|
async function walkMarkdown(dir) {
|
|
2766
3091
|
let entries;
|
|
2767
3092
|
try {
|
|
2768
|
-
entries = await
|
|
3093
|
+
entries = await import_promises17.default.readdir(dir, { withFileTypes: true });
|
|
2769
3094
|
} catch {
|
|
2770
3095
|
return [];
|
|
2771
3096
|
}
|
|
2772
3097
|
const out = [];
|
|
2773
3098
|
for (const e of entries) {
|
|
2774
|
-
const full =
|
|
3099
|
+
const full = import_node_path15.default.join(dir, e.name);
|
|
2775
3100
|
if (e.isDirectory()) out.push(...await walkMarkdown(full));
|
|
2776
3101
|
else if (e.isFile() && /\.mdx?$/.test(e.name) && !/-template\.mdx?$/.test(e.name)) out.push(full);
|
|
2777
3102
|
}
|
|
@@ -2801,11 +3126,11 @@ async function lintCollections(scope) {
|
|
|
2801
3126
|
let warnCount = 0;
|
|
2802
3127
|
for (const coll of collections) {
|
|
2803
3128
|
for (const full of await walkMarkdown(coll.path)) {
|
|
2804
|
-
const rel =
|
|
3129
|
+
const rel = import_node_path15.default.relative(coll.path, full);
|
|
2805
3130
|
const label = `${coll.name}/${rel}`;
|
|
2806
3131
|
let findings = [];
|
|
2807
3132
|
try {
|
|
2808
|
-
const parsed = parseArticle(await
|
|
3133
|
+
const parsed = parseArticle(await import_promises17.default.readFile(full, "utf8"));
|
|
2809
3134
|
findings = lintArticle(parsed);
|
|
2810
3135
|
} catch (err) {
|
|
2811
3136
|
findings = [{ level: "error", rule: "parse", message: `could not parse: ${err.message}` }];
|
|
@@ -2885,6 +3210,7 @@ init_registry2();
|
|
|
2885
3210
|
DiffwikiError,
|
|
2886
3211
|
InvalidExportSelectionError,
|
|
2887
3212
|
InvalidTargetError,
|
|
3213
|
+
META_KINDS,
|
|
2888
3214
|
NATIVE_ENGINE,
|
|
2889
3215
|
NoDefaultCollectionError,
|
|
2890
3216
|
PROJECT_CONFIG_FILE,
|
|
@@ -2906,10 +3232,13 @@ init_registry2();
|
|
|
2906
3232
|
configPath,
|
|
2907
3233
|
createArticle,
|
|
2908
3234
|
createCollection,
|
|
3235
|
+
createMetaRecord,
|
|
2909
3236
|
currentBranch,
|
|
2910
3237
|
deregisterPlugin,
|
|
2911
3238
|
doctor,
|
|
2912
3239
|
emitPluginEvent,
|
|
3240
|
+
ensureArticleMeta,
|
|
3241
|
+
ensureCollectionMeta,
|
|
2913
3242
|
ensureGitRepo,
|
|
2914
3243
|
ensureHome,
|
|
2915
3244
|
ensurePluginRoot,
|
|
@@ -2921,6 +3250,7 @@ init_registry2();
|
|
|
2921
3250
|
getConfigValue,
|
|
2922
3251
|
getLogger,
|
|
2923
3252
|
gitToplevel,
|
|
3253
|
+
initMeta,
|
|
2924
3254
|
initRepoWiki,
|
|
2925
3255
|
installPlugin,
|
|
2926
3256
|
invalidateLinkGraph,
|
|
@@ -2937,11 +3267,14 @@ init_registry2();
|
|
|
2937
3267
|
listTemplates,
|
|
2938
3268
|
loadSearchProvider,
|
|
2939
3269
|
localGraph,
|
|
3270
|
+
metaDir,
|
|
3271
|
+
mirrorSavedArticle,
|
|
2940
3272
|
nativeSearch,
|
|
2941
3273
|
parseAddTarget,
|
|
2942
3274
|
parseArticle,
|
|
2943
3275
|
parseFrontmatter,
|
|
2944
3276
|
parsePathTarget,
|
|
3277
|
+
patchArticle,
|
|
2945
3278
|
pluginsDir,
|
|
2946
3279
|
pluginsRegistryPath,
|
|
2947
3280
|
query,
|
|
@@ -2957,6 +3290,7 @@ init_registry2();
|
|
|
2957
3290
|
registerCollection,
|
|
2958
3291
|
registerPlugin,
|
|
2959
3292
|
registryPath,
|
|
3293
|
+
reindexMeta,
|
|
2960
3294
|
removeArticle,
|
|
2961
3295
|
removeCollection,
|
|
2962
3296
|
removePlugin,
|
|
@@ -2990,9 +3324,11 @@ init_registry2();
|
|
|
2990
3324
|
toTreeNodes,
|
|
2991
3325
|
tokenize,
|
|
2992
3326
|
unregisterCollection,
|
|
3327
|
+
updateArticle,
|
|
2993
3328
|
updateArticleBody,
|
|
2994
3329
|
upsertPlugin,
|
|
2995
3330
|
upstreamStatus,
|
|
3331
|
+
validateMetaArticlePath,
|
|
2996
3332
|
writeConfig,
|
|
2997
3333
|
writePluginRegistry,
|
|
2998
3334
|
writeRegistry
|