diffwiki-core 0.14.0 → 0.15.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/bm25.cjs +6 -0
- package/dist/bm25.js +1 -1
- package/dist/chunk-6MW6KSS4.js +278 -0
- package/dist/{chunk-FTUF6IXS.js → chunk-TK2373AM.js} +6 -0
- package/dist/{chunk-7V2DIQNL.js → chunk-TXM4OGBA.js} +12 -3
- package/dist/{events-AVD3G5AX.js → events-K3MKWACA.js} +1 -1
- package/dist/frontmatter-ghMCpLNf.d.cts +585 -0
- package/dist/frontmatter-ghMCpLNf.d.ts +585 -0
- package/dist/index.cjs +303 -195
- package/dist/index.d.cts +69 -359
- package/dist/index.d.ts +69 -359
- package/dist/index.js +200 -189
- package/dist/plugin.cjs +134 -2
- package/dist/plugin.d.cts +54 -177
- package/dist/plugin.d.ts +54 -177
- package/dist/plugin.js +9 -3
- package/package.json +1 -1
- package/dist/chunk-EEETAETA.js +0 -145
package/dist/index.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
rankDocs,
|
|
3
3
|
tokenize
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-TK2373AM.js";
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
parseArticle,
|
|
7
|
+
parseFrontmatter,
|
|
8
|
+
runProcess,
|
|
9
|
+
serializeArticle,
|
|
10
|
+
stripFrontmatter
|
|
11
|
+
} from "./chunk-6MW6KSS4.js";
|
|
8
12
|
import {
|
|
9
13
|
ArticleNotFoundError,
|
|
10
14
|
CollectionAlreadyAddedError,
|
|
@@ -43,28 +47,72 @@ import {
|
|
|
43
47
|
removeCollection,
|
|
44
48
|
resolveHome,
|
|
45
49
|
resolveLogLevel,
|
|
50
|
+
templatesDir,
|
|
46
51
|
unregisterCollection,
|
|
47
52
|
upsertPlugin,
|
|
48
53
|
writePluginRegistry,
|
|
49
54
|
writeRegistry
|
|
50
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-TXM4OGBA.js";
|
|
51
56
|
|
|
52
|
-
// src/
|
|
57
|
+
// src/templates.ts
|
|
53
58
|
import fs from "fs/promises";
|
|
59
|
+
import path from "path";
|
|
60
|
+
async function seedTemplates(srcDir) {
|
|
61
|
+
const dir = templatesDir();
|
|
62
|
+
await fs.mkdir(dir, { recursive: true });
|
|
63
|
+
const files = (await fs.readdir(srcDir)).filter((f) => f.endsWith(".md")).sort();
|
|
64
|
+
const seeded = [];
|
|
65
|
+
const skipped = [];
|
|
66
|
+
for (const file of files) {
|
|
67
|
+
const dest = path.join(dir, file);
|
|
68
|
+
try {
|
|
69
|
+
await fs.access(dest);
|
|
70
|
+
skipped.push(file);
|
|
71
|
+
continue;
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
await fs.copyFile(path.join(srcDir, file), dest);
|
|
75
|
+
seeded.push(file);
|
|
76
|
+
}
|
|
77
|
+
return { seeded, skipped, dir };
|
|
78
|
+
}
|
|
79
|
+
async function listTemplateShapes(srcDir) {
|
|
80
|
+
const files = await fs.readdir(srcDir);
|
|
81
|
+
return files.filter((f) => f.endsWith("-template.md")).map((f) => f.replace(/-template\.md$/, "")).sort();
|
|
82
|
+
}
|
|
83
|
+
async function listTemplates(srcDir) {
|
|
84
|
+
const files = (await fs.readdir(srcDir)).filter((f) => f.endsWith("-template.md")).sort();
|
|
85
|
+
const out = [];
|
|
86
|
+
for (const file of files) {
|
|
87
|
+
let data = {};
|
|
88
|
+
try {
|
|
89
|
+
({ data } = parseFrontmatter(await fs.readFile(path.join(srcDir, file), "utf8")));
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
const shape = typeof data.shape === "string" && data.shape ? data.shape : file.replace(/-template\.md$/, "");
|
|
93
|
+
const purpose = typeof data.purpose === "string" ? data.purpose : "";
|
|
94
|
+
const budget = Array.isArray(data.budget) && data.budget.length === 2 ? [Number(data.budget[0]), Number(data.budget[1])] : void 0;
|
|
95
|
+
out.push({ shape, purpose, budget, file });
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/config.ts
|
|
101
|
+
import fs2 from "fs/promises";
|
|
54
102
|
function isMissing(err) {
|
|
55
103
|
return err?.code === "ENOENT";
|
|
56
104
|
}
|
|
57
105
|
async function readConfig() {
|
|
58
106
|
try {
|
|
59
|
-
return JSON.parse(await
|
|
107
|
+
return JSON.parse(await fs2.readFile(configPath(), "utf8"));
|
|
60
108
|
} catch (err) {
|
|
61
109
|
if (isMissing(err)) return {};
|
|
62
110
|
throw err;
|
|
63
111
|
}
|
|
64
112
|
}
|
|
65
113
|
async function writeConfig(config) {
|
|
66
|
-
await
|
|
67
|
-
await
|
|
114
|
+
await fs2.mkdir(resolveHome(), { recursive: true });
|
|
115
|
+
await fs2.writeFile(configPath(), `${JSON.stringify(config, null, 2)}
|
|
68
116
|
`, "utf8");
|
|
69
117
|
}
|
|
70
118
|
async function setConfigValue(key, value) {
|
|
@@ -106,77 +154,12 @@ function slugifyPath(input) {
|
|
|
106
154
|
return segs.length > 0 ? segs.join("/") : "untitled";
|
|
107
155
|
}
|
|
108
156
|
|
|
109
|
-
// src/frontmatter.ts
|
|
110
|
-
import matter from "gray-matter";
|
|
111
|
-
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
112
|
-
var YAML_ENGINE = {
|
|
113
|
-
parse: (input) => {
|
|
114
|
-
const data = parseYaml(input);
|
|
115
|
-
return data && typeof data === "object" ? data : {};
|
|
116
|
-
},
|
|
117
|
-
stringify: (data) => stringifyYaml(data)
|
|
118
|
-
};
|
|
119
|
-
var MATTER_OPTS = { engines: { yaml: YAML_ENGINE } };
|
|
120
|
-
function coerceAudience(value) {
|
|
121
|
-
return value === "private" || value === "system" ? value : "public";
|
|
122
|
-
}
|
|
123
|
-
function coerceStatus(value) {
|
|
124
|
-
return value === "draft" || value === "deprecated" ? value : "published";
|
|
125
|
-
}
|
|
126
|
-
function parseFallback(raw) {
|
|
127
|
-
const fenceRe = /^---[ \t]*\n([\s\S]*?)\n---[ \t]*\n/;
|
|
128
|
-
const fenceMatch = fenceRe.exec(raw);
|
|
129
|
-
let title = "";
|
|
130
|
-
let audience = "public";
|
|
131
|
-
let status = "published";
|
|
132
|
-
let body;
|
|
133
|
-
const clean = (s) => s.trim().replace(/^["']|["']$/g, "");
|
|
134
|
-
if (fenceMatch) {
|
|
135
|
-
const inner = fenceMatch[1];
|
|
136
|
-
const titleMatch = /^title:\s*(.+)$/m.exec(inner);
|
|
137
|
-
if (titleMatch) {
|
|
138
|
-
title = clean(titleMatch[1]);
|
|
139
|
-
}
|
|
140
|
-
const audienceMatch = /^audience:\s*(.+)$/m.exec(inner);
|
|
141
|
-
if (audienceMatch) {
|
|
142
|
-
audience = coerceAudience(clean(audienceMatch[1]));
|
|
143
|
-
}
|
|
144
|
-
const statusMatch = /^status:\s*(.+)$/m.exec(inner);
|
|
145
|
-
if (statusMatch) {
|
|
146
|
-
status = coerceStatus(clean(statusMatch[1]));
|
|
147
|
-
}
|
|
148
|
-
body = raw.slice(fenceMatch[0].length);
|
|
149
|
-
} else {
|
|
150
|
-
body = raw;
|
|
151
|
-
}
|
|
152
|
-
return { title, tags: [], audience, status, body: body.replace(/^\n+/, "") };
|
|
153
|
-
}
|
|
154
|
-
function parseArticle(raw) {
|
|
155
|
-
try {
|
|
156
|
-
const { data, content } = matter(raw, MATTER_OPTS);
|
|
157
|
-
const title = typeof data.title === "string" ? data.title : "";
|
|
158
|
-
const tags = Array.isArray(data.tags) ? data.tags.map((t) => String(t)) : [];
|
|
159
|
-
const created = typeof data.created === "string" ? data.created : void 0;
|
|
160
|
-
const audience = coerceAudience(data.audience);
|
|
161
|
-
const status = coerceStatus(data.status);
|
|
162
|
-
return { title, tags, created, audience, status, body: content.replace(/^\n+/, "") };
|
|
163
|
-
} catch {
|
|
164
|
-
return parseFallback(raw);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
function serializeArticle(article) {
|
|
168
|
-
const front = { title: article.title, tags: article.tags };
|
|
169
|
-
if (article.audience === "private" || article.audience === "system") front.audience = article.audience;
|
|
170
|
-
if (article.status === "draft" || article.status === "deprecated") front.status = article.status;
|
|
171
|
-
return matter.stringify(article.body, front, MATTER_OPTS);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
157
|
// src/articles.ts
|
|
175
|
-
import
|
|
176
|
-
import
|
|
158
|
+
import fs3 from "fs/promises";
|
|
159
|
+
import path2 from "path";
|
|
177
160
|
async function fireHook(event, collection, relPath) {
|
|
178
161
|
try {
|
|
179
|
-
const { emitPluginEvent: emitPluginEvent2 } = await import("./events-
|
|
162
|
+
const { emitPluginEvent: emitPluginEvent2 } = await import("./events-K3MKWACA.js");
|
|
180
163
|
await emitPluginEvent2(event, collection, relPath);
|
|
181
164
|
} catch {
|
|
182
165
|
}
|
|
@@ -218,24 +201,24 @@ function ensureMdPath(file) {
|
|
|
218
201
|
async function createArticle(opts) {
|
|
219
202
|
const { collection, title } = parseAddTarget(opts.target);
|
|
220
203
|
const { name, dir } = await resolveCollectionDir(collection);
|
|
221
|
-
const filePath =
|
|
204
|
+
const filePath = path2.join(dir, `${slugifyPath(title)}.md`);
|
|
222
205
|
const body = opts.body ?? `# ${title}
|
|
223
206
|
`;
|
|
224
|
-
await
|
|
225
|
-
await
|
|
207
|
+
await fs3.mkdir(path2.dirname(filePath), { recursive: true });
|
|
208
|
+
await fs3.writeFile(
|
|
226
209
|
filePath,
|
|
227
210
|
serializeArticle({ title, tags: opts.tags, body, audience: opts.audience, status: opts.status }),
|
|
228
211
|
"utf8"
|
|
229
212
|
);
|
|
230
|
-
void fireHook("article-created", name,
|
|
213
|
+
void fireHook("article-created", name, path2.relative(dir, filePath));
|
|
231
214
|
return { title, tags: opts.tags, path: filePath, collection: name };
|
|
232
215
|
}
|
|
233
216
|
async function loadArticle(target) {
|
|
234
217
|
const { collection, file } = parsePathTarget(target);
|
|
235
218
|
const { dir } = await resolveCollectionDir(collection);
|
|
236
|
-
const filePath =
|
|
219
|
+
const filePath = path2.join(dir, ensureMdPath(file));
|
|
237
220
|
try {
|
|
238
|
-
return { filePath, dir, raw: await
|
|
221
|
+
return { filePath, dir, raw: await fs3.readFile(filePath, "utf8"), collection };
|
|
239
222
|
} catch (err) {
|
|
240
223
|
if (err.code === "ENOENT") throw new ArticleNotFoundError(target);
|
|
241
224
|
throw err;
|
|
@@ -244,7 +227,7 @@ async function loadArticle(target) {
|
|
|
244
227
|
async function updateArticleBody(target, body) {
|
|
245
228
|
const { filePath, dir, raw, collection } = await loadArticle(target);
|
|
246
229
|
const parsed = parseArticle(raw);
|
|
247
|
-
await
|
|
230
|
+
await fs3.writeFile(
|
|
248
231
|
filePath,
|
|
249
232
|
serializeArticle({
|
|
250
233
|
title: parsed.title,
|
|
@@ -255,14 +238,14 @@ async function updateArticleBody(target, body) {
|
|
|
255
238
|
}),
|
|
256
239
|
"utf8"
|
|
257
240
|
);
|
|
258
|
-
void fireHook("article-updated", collection,
|
|
241
|
+
void fireHook("article-updated", collection, path2.relative(dir, filePath));
|
|
259
242
|
return { title: parsed.title, tags: parsed.tags, path: filePath, collection };
|
|
260
243
|
}
|
|
261
244
|
async function mutateTags(target, fn) {
|
|
262
245
|
const { filePath, dir, raw, collection } = await loadArticle(target);
|
|
263
246
|
const parsed = parseArticle(raw);
|
|
264
247
|
const tags = fn(parsed.tags);
|
|
265
|
-
await
|
|
248
|
+
await fs3.writeFile(
|
|
266
249
|
filePath,
|
|
267
250
|
serializeArticle({
|
|
268
251
|
title: parsed.title,
|
|
@@ -273,7 +256,7 @@ async function mutateTags(target, fn) {
|
|
|
273
256
|
}),
|
|
274
257
|
"utf8"
|
|
275
258
|
);
|
|
276
|
-
void fireHook("article-updated", collection,
|
|
259
|
+
void fireHook("article-updated", collection, path2.relative(dir, filePath));
|
|
277
260
|
return { title: parsed.title, tags, path: filePath, collection };
|
|
278
261
|
}
|
|
279
262
|
function addTags(target, tags) {
|
|
@@ -291,31 +274,31 @@ async function setArticleMeta(target, meta) {
|
|
|
291
274
|
const parsed = parseArticle(raw);
|
|
292
275
|
const audience = meta.audience ?? parsed.audience;
|
|
293
276
|
const status = meta.status ?? parsed.status;
|
|
294
|
-
await
|
|
277
|
+
await fs3.writeFile(
|
|
295
278
|
filePath,
|
|
296
279
|
serializeArticle({ title: parsed.title, tags: parsed.tags, audience, status, body: parsed.body }),
|
|
297
280
|
"utf8"
|
|
298
281
|
);
|
|
299
|
-
void fireHook("article-updated", collection,
|
|
282
|
+
void fireHook("article-updated", collection, path2.relative(dir, filePath));
|
|
300
283
|
return { title: parsed.title, tags: parsed.tags, path: filePath, collection };
|
|
301
284
|
}
|
|
302
285
|
async function removeArticle(target) {
|
|
303
286
|
const { filePath, dir, collection } = await loadArticle(target);
|
|
304
|
-
await
|
|
305
|
-
void fireHook("article-removed", collection,
|
|
287
|
+
await fs3.rm(filePath);
|
|
288
|
+
void fireHook("article-removed", collection, path2.relative(dir, filePath));
|
|
306
289
|
return filePath;
|
|
307
290
|
}
|
|
308
291
|
|
|
309
292
|
// src/repo.ts
|
|
310
|
-
import
|
|
311
|
-
import
|
|
293
|
+
import fs5 from "fs/promises";
|
|
294
|
+
import path4 from "path";
|
|
312
295
|
import { execFile as execFile2 } from "child_process";
|
|
313
296
|
import { promisify as promisify2 } from "util";
|
|
314
|
-
import { parse as
|
|
297
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
315
298
|
|
|
316
299
|
// src/git.ts
|
|
317
|
-
import
|
|
318
|
-
import
|
|
300
|
+
import fs4 from "fs/promises";
|
|
301
|
+
import path3 from "path";
|
|
319
302
|
import { execFile } from "child_process";
|
|
320
303
|
import { promisify } from "util";
|
|
321
304
|
var execFileAsync = promisify(execFile);
|
|
@@ -340,8 +323,8 @@ async function isLinkedWorktree(cwd) {
|
|
|
340
323
|
const gitDir = await git(cwd, ["rev-parse", "--absolute-git-dir"]);
|
|
341
324
|
const commonDir = await git(cwd, ["rev-parse", "--git-common-dir"]);
|
|
342
325
|
if (!gitDir || !commonDir) return false;
|
|
343
|
-
const commonAbs =
|
|
344
|
-
return
|
|
326
|
+
const commonAbs = path3.isAbsolute(commonDir) ? commonDir : path3.resolve(cwd, commonDir);
|
|
327
|
+
return path3.resolve(gitDir) !== path3.resolve(commonAbs);
|
|
345
328
|
}
|
|
346
329
|
async function repoRemoteName(cwd) {
|
|
347
330
|
const url = await git(cwd, ["remote", "get-url", "origin"]);
|
|
@@ -383,8 +366,8 @@ var timestampCache = /* @__PURE__ */ new Map();
|
|
|
383
366
|
async function fileTimestamps(absPath) {
|
|
384
367
|
const cached2 = timestampCache.get(absPath);
|
|
385
368
|
if (cached2) return cached2;
|
|
386
|
-
const dir =
|
|
387
|
-
const base =
|
|
369
|
+
const dir = path3.dirname(absPath);
|
|
370
|
+
const base = path3.basename(absPath);
|
|
388
371
|
const [addLog, lastLog] = await Promise.all([
|
|
389
372
|
git(dir, ["log", "--follow", "--diff-filter=A", "--format=%aI", "--", base]),
|
|
390
373
|
git(dir, ["log", "-1", "--format=%aI", "--", base])
|
|
@@ -395,7 +378,7 @@ async function fileTimestamps(absPath) {
|
|
|
395
378
|
let updated = gitUpdated;
|
|
396
379
|
if (!created || !updated) {
|
|
397
380
|
try {
|
|
398
|
-
const st = await
|
|
381
|
+
const st = await fs4.stat(absPath);
|
|
399
382
|
if (!updated) updated = st.mtime.toISOString();
|
|
400
383
|
if (!created) created = isEpochish(st.birthtime) ? st.mtime.toISOString() : st.birthtime.toISOString();
|
|
401
384
|
} catch {
|
|
@@ -412,18 +395,18 @@ var REPO_CONFIG_FILE = "diffwiki.yaml";
|
|
|
412
395
|
async function detectRepoName(cwd) {
|
|
413
396
|
try {
|
|
414
397
|
const { stdout } = await execFileAsync2("git", ["rev-parse", "--show-toplevel"], { cwd });
|
|
415
|
-
return
|
|
398
|
+
return path4.basename(stdout.trim());
|
|
416
399
|
} catch {
|
|
417
|
-
return
|
|
400
|
+
return path4.basename(path4.resolve(cwd));
|
|
418
401
|
}
|
|
419
402
|
}
|
|
420
403
|
async function readRepoConfig(cwd) {
|
|
421
404
|
try {
|
|
422
|
-
const raw = await
|
|
423
|
-
const parsed =
|
|
405
|
+
const raw = await fs5.readFile(path4.join(cwd, REPO_CONFIG_FILE), "utf8");
|
|
406
|
+
const parsed = parseYaml(raw) ?? {};
|
|
424
407
|
if (!parsed.path) return void 0;
|
|
425
408
|
return {
|
|
426
|
-
collection: parsed.collection ??
|
|
409
|
+
collection: parsed.collection ?? path4.basename(path4.resolve(cwd)),
|
|
427
410
|
path: parsed.path
|
|
428
411
|
};
|
|
429
412
|
} catch (err) {
|
|
@@ -432,9 +415,9 @@ async function readRepoConfig(cwd) {
|
|
|
432
415
|
}
|
|
433
416
|
}
|
|
434
417
|
async function initRepoWiki(opts) {
|
|
435
|
-
const cwd =
|
|
418
|
+
const cwd = path4.resolve(opts.cwd);
|
|
436
419
|
const wikiPath = opts.wikiPath ?? "wiki";
|
|
437
|
-
const absWiki =
|
|
420
|
+
const absWiki = path4.join(cwd, wikiPath);
|
|
438
421
|
let name = opts.collection ?? await detectRepoName(cwd);
|
|
439
422
|
const existing = await findCollection(name);
|
|
440
423
|
if (existing && existing.path !== absWiki) {
|
|
@@ -449,9 +432,9 @@ async function initRepoWiki(opts) {
|
|
|
449
432
|
n += 1;
|
|
450
433
|
}
|
|
451
434
|
}
|
|
452
|
-
await
|
|
435
|
+
await fs5.mkdir(absWiki, { recursive: true });
|
|
453
436
|
const config = { collection: name, path: wikiPath };
|
|
454
|
-
await
|
|
437
|
+
await fs5.writeFile(path4.join(cwd, REPO_CONFIG_FILE), stringifyYaml(config), "utf8");
|
|
455
438
|
const entry = {
|
|
456
439
|
name,
|
|
457
440
|
type: "repo",
|
|
@@ -462,15 +445,15 @@ async function initRepoWiki(opts) {
|
|
|
462
445
|
return entry;
|
|
463
446
|
}
|
|
464
447
|
async function addExternalCollection(opts) {
|
|
465
|
-
const cwd =
|
|
448
|
+
const cwd = path4.resolve(opts.cwd);
|
|
466
449
|
const root = await ensureGitRepo(cwd);
|
|
467
450
|
if (await isLinkedWorktree(cwd)) throw new WorktreeNotAllowedError(root);
|
|
468
451
|
const id = await repoId(root) ?? `path:${root}`;
|
|
469
452
|
const already = await findCollectionById(id);
|
|
470
453
|
if (already) throw new CollectionAlreadyAddedError(already.name);
|
|
471
454
|
const docsRel = opts.docsDir ?? "docs";
|
|
472
|
-
const contentDir =
|
|
473
|
-
let name = await repoRemoteName(root) ??
|
|
455
|
+
const contentDir = path4.join(root, docsRel);
|
|
456
|
+
let name = await repoRemoteName(root) ?? path4.basename(root);
|
|
474
457
|
if (await findCollection(name)) {
|
|
475
458
|
let n = 2;
|
|
476
459
|
while (await findCollection(`${name}-${n}`)) n += 1;
|
|
@@ -489,14 +472,14 @@ async function addExternalCollection(opts) {
|
|
|
489
472
|
return entry;
|
|
490
473
|
}
|
|
491
474
|
async function resolveCwdCollection(cwd) {
|
|
492
|
-
const abs =
|
|
475
|
+
const abs = path4.resolve(cwd);
|
|
493
476
|
const root = await gitToplevel(abs);
|
|
494
477
|
if (root) {
|
|
495
478
|
const id = await repoId(root) ?? `path:${root}`;
|
|
496
479
|
const byId = await findCollectionById(id);
|
|
497
480
|
if (byId) return byId;
|
|
498
481
|
}
|
|
499
|
-
const within = (base) => abs === base || abs.startsWith(base +
|
|
482
|
+
const within = (base) => abs === base || abs.startsWith(base + path4.sep);
|
|
500
483
|
const all = await listCollections();
|
|
501
484
|
let best;
|
|
502
485
|
let bestLen = -1;
|
|
@@ -520,10 +503,10 @@ async function collectionGitStatus(entry) {
|
|
|
520
503
|
}
|
|
521
504
|
|
|
522
505
|
// src/project.ts
|
|
523
|
-
import
|
|
506
|
+
import fs6 from "fs/promises";
|
|
524
507
|
import os from "os";
|
|
525
|
-
import
|
|
526
|
-
import { parse as
|
|
508
|
+
import path5 from "path";
|
|
509
|
+
import { parse as parseYaml2, parseDocument } from "yaml";
|
|
527
510
|
var PROJECT_CONFIG_FILE = "diffwiki.yaml";
|
|
528
511
|
function normaliseSite(raw) {
|
|
529
512
|
if (!raw || typeof raw !== "object") return void 0;
|
|
@@ -577,12 +560,12 @@ function normaliseContext(raw) {
|
|
|
577
560
|
async function readProjectConfig(dir) {
|
|
578
561
|
let raw;
|
|
579
562
|
try {
|
|
580
|
-
raw = await
|
|
563
|
+
raw = await fs6.readFile(path5.join(dir, PROJECT_CONFIG_FILE), "utf8");
|
|
581
564
|
} catch (err) {
|
|
582
565
|
if (err.code === "ENOENT") return void 0;
|
|
583
566
|
throw err;
|
|
584
567
|
}
|
|
585
|
-
const parsed =
|
|
568
|
+
const parsed = parseYaml2(raw) ?? {};
|
|
586
569
|
const collections = [];
|
|
587
570
|
if (Array.isArray(parsed.collections)) {
|
|
588
571
|
for (const c of parsed.collections) {
|
|
@@ -598,7 +581,7 @@ async function readProjectConfig(dir) {
|
|
|
598
581
|
}
|
|
599
582
|
collections.sort((a, b) => (a.order ?? Number.MAX_SAFE_INTEGER) - (b.order ?? Number.MAX_SAFE_INTEGER));
|
|
600
583
|
} else if (typeof parsed.path === "string") {
|
|
601
|
-
const name = typeof parsed.collection === "string" ? parsed.collection :
|
|
584
|
+
const name = typeof parsed.collection === "string" ? parsed.collection : path5.basename(path5.resolve(dir));
|
|
602
585
|
collections.push({ name, path: parsed.path });
|
|
603
586
|
}
|
|
604
587
|
const site = normaliseSite(parsed.site);
|
|
@@ -608,15 +591,28 @@ async function readProjectConfig(dir) {
|
|
|
608
591
|
async function readContextCollections(dir) {
|
|
609
592
|
return (await readProjectConfig(dir))?.context?.collections ?? [];
|
|
610
593
|
}
|
|
594
|
+
async function setContextCollections(dir, collections) {
|
|
595
|
+
const file = path5.join(dir, PROJECT_CONFIG_FILE);
|
|
596
|
+
let raw = "";
|
|
597
|
+
try {
|
|
598
|
+
raw = await fs6.readFile(file, "utf8");
|
|
599
|
+
} catch (err) {
|
|
600
|
+
if (err.code !== "ENOENT") throw err;
|
|
601
|
+
}
|
|
602
|
+
const doc = parseDocument(raw);
|
|
603
|
+
doc.setIn(["context", "collections"], collections);
|
|
604
|
+
await fs6.writeFile(file, doc.toString(), "utf8");
|
|
605
|
+
return file;
|
|
606
|
+
}
|
|
611
607
|
async function resolveProjectCollections(dir) {
|
|
612
608
|
const config = await readProjectConfig(dir);
|
|
613
609
|
if (!config) return [];
|
|
614
|
-
const root =
|
|
610
|
+
const root = path5.resolve(dir);
|
|
615
611
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
616
612
|
return config.collections.map((c) => ({
|
|
617
613
|
name: c.name,
|
|
618
614
|
type: "repo",
|
|
619
|
-
path:
|
|
615
|
+
path: path5.resolve(root, c.path),
|
|
620
616
|
createdAt
|
|
621
617
|
}));
|
|
622
618
|
}
|
|
@@ -626,11 +622,11 @@ async function readSiteConfig(dir) {
|
|
|
626
622
|
async function seedProjectHome(dir) {
|
|
627
623
|
const entries = await resolveProjectCollections(dir);
|
|
628
624
|
if (entries.length === 0) {
|
|
629
|
-
throw new Error(`No collections to render from diffwiki.yaml in ${
|
|
625
|
+
throw new Error(`No collections to render from diffwiki.yaml in ${path5.resolve(dir)}`);
|
|
630
626
|
}
|
|
631
|
-
const home = await
|
|
632
|
-
await
|
|
633
|
-
|
|
627
|
+
const home = await fs6.mkdtemp(path5.join(os.tmpdir(), "diffwiki-project-"));
|
|
628
|
+
await fs6.writeFile(
|
|
629
|
+
path5.join(home, "registry.json"),
|
|
634
630
|
`${JSON.stringify({ version: 1, collections: entries }, null, 2)}
|
|
635
631
|
`,
|
|
636
632
|
"utf8"
|
|
@@ -639,8 +635,8 @@ async function seedProjectHome(dir) {
|
|
|
639
635
|
}
|
|
640
636
|
|
|
641
637
|
// src/browse.ts
|
|
642
|
-
import
|
|
643
|
-
import
|
|
638
|
+
import fs7 from "fs/promises";
|
|
639
|
+
import path6 from "path";
|
|
644
640
|
var INDEX_RE = /^(index|readme)\.mdx?$/i;
|
|
645
641
|
function buildArticleTree(relPaths) {
|
|
646
642
|
const root = {};
|
|
@@ -684,7 +680,7 @@ function buildArticleTree(relPaths) {
|
|
|
684
680
|
async function collectRelPaths(dir, rel = "") {
|
|
685
681
|
let dirents;
|
|
686
682
|
try {
|
|
687
|
-
dirents = await
|
|
683
|
+
dirents = await fs7.readdir(dir, { withFileTypes: true });
|
|
688
684
|
} catch {
|
|
689
685
|
return [];
|
|
690
686
|
}
|
|
@@ -693,7 +689,7 @@ async function collectRelPaths(dir, rel = "") {
|
|
|
693
689
|
if (d.name.startsWith(".")) continue;
|
|
694
690
|
const childRel = rel ? `${rel}/${d.name}` : d.name;
|
|
695
691
|
if (d.isDirectory()) {
|
|
696
|
-
out.push(...await collectRelPaths(
|
|
692
|
+
out.push(...await collectRelPaths(path6.join(dir, d.name), childRel));
|
|
697
693
|
} else if (/\.(mdx?)$/.test(d.name)) {
|
|
698
694
|
out.push(childRel);
|
|
699
695
|
}
|
|
@@ -702,7 +698,7 @@ async function collectRelPaths(dir, rel = "") {
|
|
|
702
698
|
}
|
|
703
699
|
async function readdirSafe(dir) {
|
|
704
700
|
try {
|
|
705
|
-
return await
|
|
701
|
+
return await fs7.readdir(dir);
|
|
706
702
|
} catch {
|
|
707
703
|
return [];
|
|
708
704
|
}
|
|
@@ -720,7 +716,7 @@ var CHEAP_META_BYTES = 8192;
|
|
|
720
716
|
async function cheapMeta(absPath) {
|
|
721
717
|
let handle;
|
|
722
718
|
try {
|
|
723
|
-
handle = await
|
|
719
|
+
handle = await fs7.open(absPath, "r");
|
|
724
720
|
const buf = Buffer.alloc(CHEAP_META_BYTES);
|
|
725
721
|
const { bytesRead } = await handle.read(buf, 0, CHEAP_META_BYTES, 0);
|
|
726
722
|
const snippet = buf.subarray(0, bytesRead).toString("utf8");
|
|
@@ -754,10 +750,10 @@ async function enrichTitles(nodes, collDir) {
|
|
|
754
750
|
let status = node.status;
|
|
755
751
|
let order = node.order;
|
|
756
752
|
if (node.slug) {
|
|
757
|
-
const dir =
|
|
753
|
+
const dir = path6.join(collDir, node.slug);
|
|
758
754
|
const idx = pickIndexFile(await readdirSafe(dir));
|
|
759
755
|
if (idx) {
|
|
760
|
-
const meta = await cheapMeta(
|
|
756
|
+
const meta = await cheapMeta(path6.join(dir, idx));
|
|
761
757
|
title = meta.title ?? title;
|
|
762
758
|
audience = meta.audience ?? audience;
|
|
763
759
|
status = meta.status ?? status;
|
|
@@ -767,7 +763,7 @@ async function enrichTitles(nodes, collDir) {
|
|
|
767
763
|
return { ...node, title, audience, status, order, children };
|
|
768
764
|
}
|
|
769
765
|
for (const ext of [".mdx", ".md"]) {
|
|
770
|
-
const meta = await cheapMeta(
|
|
766
|
+
const meta = await cheapMeta(path6.join(collDir, `${node.slug}${ext}`));
|
|
771
767
|
if (meta.title || meta.audience || meta.status || meta.order != null) {
|
|
772
768
|
return {
|
|
773
769
|
...node,
|
|
@@ -786,49 +782,49 @@ async function enrichTitles(nodes, collDir) {
|
|
|
786
782
|
async function collectionMeta(collDir) {
|
|
787
783
|
const idx = pickIndexFile(await readdirSafe(collDir));
|
|
788
784
|
if (!idx) return {};
|
|
789
|
-
const meta = await cheapMeta(
|
|
785
|
+
const meta = await cheapMeta(path6.join(collDir, idx));
|
|
790
786
|
return { title: meta.title, audience: meta.audience, status: meta.status };
|
|
791
787
|
}
|
|
792
788
|
async function listArticleTree(coll) {
|
|
793
789
|
const entry = await findCollection(coll);
|
|
794
790
|
if (!entry) throw new CollectionNotFoundError(coll);
|
|
795
791
|
const rels = await collectRelPaths(entry.path);
|
|
796
|
-
return enrichTitles(buildArticleTree(rels),
|
|
792
|
+
return enrichTitles(buildArticleTree(rels), path6.resolve(entry.path));
|
|
797
793
|
}
|
|
798
794
|
async function readArticle(coll, slug) {
|
|
799
795
|
const entry = await findCollection(coll);
|
|
800
796
|
if (!entry) throw new CollectionNotFoundError(coll);
|
|
801
|
-
const collDir =
|
|
797
|
+
const collDir = path6.resolve(entry.path);
|
|
802
798
|
const normalized = slug.replace(/\\/g, "/");
|
|
803
799
|
if (normalized.split("/").some((s) => s === "..")) {
|
|
804
800
|
throw new InvalidTargetError(slug, "path traversal detected");
|
|
805
801
|
}
|
|
806
802
|
const safeSlug = normalized.split("/").filter((s) => s !== "" && s !== ".").join("/");
|
|
807
|
-
const inJail = (p) => p === collDir || p.startsWith(collDir +
|
|
803
|
+
const inJail = (p) => p === collDir || p.startsWith(collDir + path6.sep);
|
|
808
804
|
let abs;
|
|
809
805
|
let isIndex = false;
|
|
810
806
|
for (const rel of safeSlug ? [`${safeSlug}.mdx`, `${safeSlug}.md`] : []) {
|
|
811
|
-
const candidate =
|
|
807
|
+
const candidate = path6.resolve(collDir, rel);
|
|
812
808
|
if (!inJail(candidate)) throw new InvalidTargetError(slug, "path traversal detected");
|
|
813
809
|
try {
|
|
814
|
-
await
|
|
810
|
+
await fs7.access(candidate);
|
|
815
811
|
abs = candidate;
|
|
816
812
|
break;
|
|
817
813
|
} catch {
|
|
818
814
|
}
|
|
819
815
|
}
|
|
820
816
|
if (!abs) {
|
|
821
|
-
const dir =
|
|
817
|
+
const dir = path6.resolve(collDir, safeSlug);
|
|
822
818
|
if (inJail(dir)) {
|
|
823
819
|
const idx = pickIndexFile(await readdirSafe(dir));
|
|
824
820
|
if (idx) {
|
|
825
|
-
abs =
|
|
821
|
+
abs = path6.join(dir, idx);
|
|
826
822
|
isIndex = true;
|
|
827
823
|
}
|
|
828
824
|
}
|
|
829
825
|
}
|
|
830
826
|
if (!abs) throw new ArticleNotFoundError(`${coll}/${safeSlug}`);
|
|
831
|
-
const parsed = parseArticle(await
|
|
827
|
+
const parsed = parseArticle(await fs7.readFile(abs, "utf8"));
|
|
832
828
|
const title = parsed.title?.trim() || (safeSlug ? safeSlug.split("/").pop() ?? safeSlug : coll);
|
|
833
829
|
return {
|
|
834
830
|
coll,
|
|
@@ -844,7 +840,7 @@ async function readArticle(coll, slug) {
|
|
|
844
840
|
}
|
|
845
841
|
|
|
846
842
|
// src/render.ts
|
|
847
|
-
import
|
|
843
|
+
import path7 from "path";
|
|
848
844
|
import { unified } from "unified";
|
|
849
845
|
import remarkParse from "remark-parse";
|
|
850
846
|
import remarkGfm from "remark-gfm";
|
|
@@ -861,7 +857,7 @@ function isNonRelativeHref(href) {
|
|
|
861
857
|
return /^[a-z][a-z0-9+.-]*:/i.test(href);
|
|
862
858
|
}
|
|
863
859
|
function rehypeRewriteLinks(ctx) {
|
|
864
|
-
const slugDir = ctx.isIndex ? ctx.slug || "." :
|
|
860
|
+
const slugDir = ctx.isIndex ? ctx.slug || "." : path7.posix.dirname(ctx.slug || ".");
|
|
865
861
|
return (tree) => {
|
|
866
862
|
visit(tree, "element", (node) => {
|
|
867
863
|
if (node.tagName !== "a") return;
|
|
@@ -871,8 +867,8 @@ function rehypeRewriteLinks(ctx) {
|
|
|
871
867
|
const suffix = suffixMatch ? suffixMatch[0] : "";
|
|
872
868
|
const pathPart = suffix ? raw.slice(0, raw.length - suffix.length) : raw;
|
|
873
869
|
if (pathPart === "") return;
|
|
874
|
-
const joined =
|
|
875
|
-
const normalized =
|
|
870
|
+
const joined = path7.posix.join(slugDir === "." ? "" : slugDir, pathPart);
|
|
871
|
+
const normalized = path7.posix.normalize(joined);
|
|
876
872
|
const toRoute = (p) => p.replace(/\.(mdx?)$/i, "").replace(/(^|\/)(index|readme)$/i, "$1").replace(/\/+$/, "").replace(/^\/+/, "");
|
|
877
873
|
if (normalized.startsWith("../")) {
|
|
878
874
|
const sibling = normalized.slice(3);
|
|
@@ -1272,19 +1268,19 @@ function resolveCollectionSelection(all, sel = {}) {
|
|
|
1272
1268
|
}
|
|
1273
1269
|
|
|
1274
1270
|
// src/query.ts
|
|
1275
|
-
import
|
|
1276
|
-
import
|
|
1271
|
+
import fs8 from "fs/promises";
|
|
1272
|
+
import path8 from "path";
|
|
1277
1273
|
var log2 = getLogger("query");
|
|
1278
1274
|
async function collectMarkdownFiles(dir) {
|
|
1279
1275
|
let entries;
|
|
1280
1276
|
try {
|
|
1281
|
-
entries = await
|
|
1277
|
+
entries = await fs8.readdir(dir, { withFileTypes: true });
|
|
1282
1278
|
} catch {
|
|
1283
1279
|
return [];
|
|
1284
1280
|
}
|
|
1285
1281
|
const files = [];
|
|
1286
1282
|
for (const entry of entries) {
|
|
1287
|
-
const full =
|
|
1283
|
+
const full = path8.join(dir, entry.name);
|
|
1288
1284
|
if (entry.isDirectory()) {
|
|
1289
1285
|
files.push(...await collectMarkdownFiles(full));
|
|
1290
1286
|
} else if (entry.isFile() && /\.mdx?$/.test(entry.name)) {
|
|
@@ -1311,12 +1307,12 @@ async function nativeSearch(term, collections, collection, hideSystem = false) {
|
|
|
1311
1307
|
const docs = [];
|
|
1312
1308
|
for (const entry of targets) {
|
|
1313
1309
|
for (const full of await collectMarkdownFiles(entry.path)) {
|
|
1314
|
-
const fallbackTitle =
|
|
1310
|
+
const fallbackTitle = path8.basename(full).replace(/\.mdx?$/, "");
|
|
1315
1311
|
let title = fallbackTitle;
|
|
1316
1312
|
let tags = [];
|
|
1317
1313
|
let body = "";
|
|
1318
1314
|
try {
|
|
1319
|
-
const parsed = parseArticle(await
|
|
1315
|
+
const parsed = parseArticle(await fs8.readFile(full, "utf8"));
|
|
1320
1316
|
if (hideSystem && parsed.audience === "system") continue;
|
|
1321
1317
|
if (parsed.title) title = parsed.title;
|
|
1322
1318
|
tags = parsed.tags;
|
|
@@ -1374,7 +1370,7 @@ async function dropSystemHits(hits) {
|
|
|
1374
1370
|
const kept = [];
|
|
1375
1371
|
for (const hit of hits) {
|
|
1376
1372
|
try {
|
|
1377
|
-
if (parseArticle(await
|
|
1373
|
+
if (parseArticle(await fs8.readFile(hit.path, "utf8")).audience === "system") continue;
|
|
1378
1374
|
} catch {
|
|
1379
1375
|
}
|
|
1380
1376
|
kept.push(hit);
|
|
@@ -1390,6 +1386,7 @@ async function search(term, opts) {
|
|
|
1390
1386
|
const showSystem = opts?.showSystem ?? await showSystemEnabled();
|
|
1391
1387
|
const hideSystem = !showSystem && wanted.length === 0;
|
|
1392
1388
|
const searchable = hideSystem ? collections.filter((c) => c.audience !== "system") : collections;
|
|
1389
|
+
const systemCollections = new Set(collections.filter((c) => c.audience === "system").map((c) => c.name));
|
|
1393
1390
|
const filterHits = (hits) => wanted.length > 0 ? hits.filter((h) => wanted.includes(h.collection)) : hits;
|
|
1394
1391
|
const engine = opts?.engine ?? await resolveDefaultEngine();
|
|
1395
1392
|
log2.debug(`resolved search engine "${engine}"`);
|
|
@@ -1407,7 +1404,7 @@ async function search(term, opts) {
|
|
|
1407
1404
|
try {
|
|
1408
1405
|
const providerOpts = { ...opts, collection: wanted.length === 1 ? wanted[0] : void 0 };
|
|
1409
1406
|
const raw = filterHits(await provider.handleSearch(term, searchable, providerOpts));
|
|
1410
|
-
const hits = hideSystem ? await dropSystemHits(raw) : raw;
|
|
1407
|
+
const hits = hideSystem ? await dropSystemHits(raw.filter((h) => !systemCollections.has(h.collection))) : raw;
|
|
1411
1408
|
return { hits, engine, fellBackToNative: false };
|
|
1412
1409
|
} catch (err) {
|
|
1413
1410
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -1421,13 +1418,13 @@ async function query(term, opts) {
|
|
|
1421
1418
|
}
|
|
1422
1419
|
|
|
1423
1420
|
// src/search.ts
|
|
1424
|
-
import
|
|
1425
|
-
import
|
|
1421
|
+
import fs9 from "fs/promises";
|
|
1422
|
+
import path9 from "path";
|
|
1426
1423
|
var isStatic2 = () => process.env.DIFFWIKI_STATIC === "1";
|
|
1427
1424
|
async function readArticleParts(collDir, slug) {
|
|
1428
1425
|
for (const ext of [".mdx", ".md"]) {
|
|
1429
1426
|
try {
|
|
1430
|
-
const parsed = parseArticle(await
|
|
1427
|
+
const parsed = parseArticle(await fs9.readFile(path9.join(collDir, `${slug}${ext}`), "utf8"));
|
|
1431
1428
|
return { tags: parsed.tags, body: parsed.body };
|
|
1432
1429
|
} catch {
|
|
1433
1430
|
}
|
|
@@ -1463,7 +1460,7 @@ async function buildSearchIndex(collection) {
|
|
|
1463
1460
|
for (const e of entries) {
|
|
1464
1461
|
try {
|
|
1465
1462
|
const collEntry = await findCollection(e.name);
|
|
1466
|
-
const collDir =
|
|
1463
|
+
const collDir = path9.resolve(collEntry?.path ?? e.path);
|
|
1467
1464
|
if (collEntry?.audience === "system") continue;
|
|
1468
1465
|
if (isStatic2()) {
|
|
1469
1466
|
const cm = await collectionMeta(collDir);
|
|
@@ -1477,15 +1474,15 @@ async function buildSearchIndex(collection) {
|
|
|
1477
1474
|
}
|
|
1478
1475
|
|
|
1479
1476
|
// src/doctor.ts
|
|
1480
|
-
import
|
|
1477
|
+
import fs11 from "fs/promises";
|
|
1481
1478
|
|
|
1482
1479
|
// src/plugins/manager.ts
|
|
1483
|
-
import
|
|
1480
|
+
import fs10 from "fs/promises";
|
|
1484
1481
|
import os2 from "os";
|
|
1485
|
-
import
|
|
1482
|
+
import path10 from "path";
|
|
1486
1483
|
async function readPackageJson(file) {
|
|
1487
1484
|
try {
|
|
1488
|
-
return JSON.parse(await
|
|
1485
|
+
return JSON.parse(await fs10.readFile(file, "utf8"));
|
|
1489
1486
|
} catch {
|
|
1490
1487
|
return null;
|
|
1491
1488
|
}
|
|
@@ -1501,7 +1498,7 @@ function binOf(manifest) {
|
|
|
1501
1498
|
async function pluginManifestDirs(nodeModules) {
|
|
1502
1499
|
let entries;
|
|
1503
1500
|
try {
|
|
1504
|
-
entries = await
|
|
1501
|
+
entries = await fs10.readdir(nodeModules);
|
|
1505
1502
|
} catch {
|
|
1506
1503
|
return [];
|
|
1507
1504
|
}
|
|
@@ -1511,44 +1508,44 @@ async function pluginManifestDirs(nodeModules) {
|
|
|
1511
1508
|
if (entry.startsWith("@")) {
|
|
1512
1509
|
let scoped;
|
|
1513
1510
|
try {
|
|
1514
|
-
scoped = await
|
|
1511
|
+
scoped = await fs10.readdir(path10.join(nodeModules, entry));
|
|
1515
1512
|
} catch {
|
|
1516
1513
|
continue;
|
|
1517
1514
|
}
|
|
1518
1515
|
for (const pkg of scoped) {
|
|
1519
1516
|
if (pkg.startsWith(".")) continue;
|
|
1520
|
-
dirs.push(
|
|
1517
|
+
dirs.push(path10.join(nodeModules, entry, pkg));
|
|
1521
1518
|
}
|
|
1522
1519
|
} else {
|
|
1523
|
-
dirs.push(
|
|
1520
|
+
dirs.push(path10.join(nodeModules, entry));
|
|
1524
1521
|
}
|
|
1525
1522
|
}
|
|
1526
1523
|
return dirs;
|
|
1527
1524
|
}
|
|
1528
1525
|
async function ensurePluginRoot() {
|
|
1529
1526
|
const dir = pluginsDir();
|
|
1530
|
-
await
|
|
1531
|
-
const pkgPath =
|
|
1527
|
+
await fs10.mkdir(dir, { recursive: true });
|
|
1528
|
+
const pkgPath = path10.join(dir, "package.json");
|
|
1532
1529
|
if (await readPackageJson(pkgPath) === null) {
|
|
1533
1530
|
const pkg = { name: "diffwiki-plugins", private: true, version: "0.0.0" };
|
|
1534
|
-
await
|
|
1531
|
+
await fs10.writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
1535
1532
|
`, "utf8");
|
|
1536
1533
|
}
|
|
1537
1534
|
return dir;
|
|
1538
1535
|
}
|
|
1539
1536
|
async function asSearchPackage(pkgDir) {
|
|
1540
|
-
const manifest = await readPackageJson(
|
|
1537
|
+
const manifest = await readPackageJson(path10.join(pkgDir, "package.json"));
|
|
1541
1538
|
if (!manifest?.name || manifest.diffwiki?.kind !== "search") return null;
|
|
1542
1539
|
const bin = binOf(manifest);
|
|
1543
1540
|
if (!bin) return null;
|
|
1544
|
-
return { name: manifest.name, version: manifest.version, binAbs:
|
|
1541
|
+
return { name: manifest.name, version: manifest.version, binAbs: path10.resolve(pkgDir, bin) };
|
|
1545
1542
|
}
|
|
1546
1543
|
async function packageNameFromSpec(spec) {
|
|
1547
1544
|
const looksLikePath = spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("~") || spec.startsWith("file:");
|
|
1548
1545
|
if (looksLikePath) {
|
|
1549
1546
|
const raw = spec.startsWith("file:") ? spec.slice("file:".length) : spec;
|
|
1550
|
-
const abs =
|
|
1551
|
-
return (await readPackageJson(
|
|
1547
|
+
const abs = path10.resolve(raw.replace(/^~(?=$|\/)/, os2.homedir()));
|
|
1548
|
+
return (await readPackageJson(path10.join(abs, "package.json")))?.name ?? null;
|
|
1552
1549
|
}
|
|
1553
1550
|
if (/^(git\+|git:|https?:|ssh:)/.test(spec)) return null;
|
|
1554
1551
|
if (spec.startsWith("@")) {
|
|
@@ -1559,10 +1556,10 @@ async function packageNameFromSpec(spec) {
|
|
|
1559
1556
|
return at <= 0 ? spec : spec.slice(0, at);
|
|
1560
1557
|
}
|
|
1561
1558
|
async function resolveSearchPackage(dir, name) {
|
|
1562
|
-
return asSearchPackage(
|
|
1559
|
+
return asSearchPackage(path10.join(dir, "node_modules", ...name.split("/")));
|
|
1563
1560
|
}
|
|
1564
1561
|
async function discoverSearchPackage(dir) {
|
|
1565
|
-
const nodeModules =
|
|
1562
|
+
const nodeModules = path10.join(dir, "node_modules");
|
|
1566
1563
|
for (const pkgDir of await pluginManifestDirs(nodeModules)) {
|
|
1567
1564
|
const found = await asSearchPackage(pkgDir);
|
|
1568
1565
|
if (found) return found;
|
|
@@ -1659,6 +1656,14 @@ async function registerPlugin(name, command) {
|
|
|
1659
1656
|
await runSetup(command, name);
|
|
1660
1657
|
return record;
|
|
1661
1658
|
}
|
|
1659
|
+
async function repairPlugins() {
|
|
1660
|
+
const enabled = (await readPluginRegistry()).plugins.filter((p) => p.enabled && p.kind === "search");
|
|
1661
|
+
const out = [];
|
|
1662
|
+
for (const p of enabled) {
|
|
1663
|
+
out.push({ name: p.name, setup: await runSetup(p.command, p.name) });
|
|
1664
|
+
}
|
|
1665
|
+
return out;
|
|
1666
|
+
}
|
|
1662
1667
|
async function removePlugin(name) {
|
|
1663
1668
|
const record = (await readPluginRegistry()).plugins.find((p) => p.name === name);
|
|
1664
1669
|
if (!record) throw new PluginError(`plugin "${name}" is not registered`);
|
|
@@ -1692,7 +1697,7 @@ async function listPlugins() {
|
|
|
1692
1697
|
// src/doctor.ts
|
|
1693
1698
|
async function exists(target) {
|
|
1694
1699
|
try {
|
|
1695
|
-
await
|
|
1700
|
+
await fs11.access(target);
|
|
1696
1701
|
return true;
|
|
1697
1702
|
} catch {
|
|
1698
1703
|
return false;
|
|
@@ -1798,15 +1803,14 @@ async function appendPluginDiagnostics(out, config) {
|
|
|
1798
1803
|
}
|
|
1799
1804
|
|
|
1800
1805
|
// src/lint.ts
|
|
1801
|
-
import
|
|
1802
|
-
import
|
|
1806
|
+
import fs12 from "fs/promises";
|
|
1807
|
+
import path11 from "path";
|
|
1803
1808
|
var BUDGETS = {
|
|
1804
1809
|
recipe: { min: 300, max: 1e3 },
|
|
1805
1810
|
"recipe-steps": { min: 500, max: 1500 },
|
|
1806
1811
|
guide: { min: 1500, max: 5e3 },
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
// Fallback for a bare `architecture` tag when system vs component isn't declared.
|
|
1812
|
+
reference: { min: 300, max: 2e3 },
|
|
1813
|
+
// Generic fallback budget for a bare `architecture` tag (no dedicated template).
|
|
1810
1814
|
architecture: { min: 1e3, max: 6e3 }
|
|
1811
1815
|
};
|
|
1812
1816
|
function estimateTokens(body) {
|
|
@@ -1815,8 +1819,7 @@ function estimateTokens(body) {
|
|
|
1815
1819
|
function inferShape(tags, body) {
|
|
1816
1820
|
const t = new Set(tags.map((x) => x.toLowerCase()));
|
|
1817
1821
|
if (t.has("guide")) return "guide";
|
|
1818
|
-
if (t.has("
|
|
1819
|
-
if (t.has("system-design") || t.has("system")) return "system-design";
|
|
1822
|
+
if (t.has("reference")) return "reference";
|
|
1820
1823
|
if (t.has("architecture")) return "architecture";
|
|
1821
1824
|
if (/^###\s+Step\b/im.test(body)) return "recipe-steps";
|
|
1822
1825
|
return "recipe";
|
|
@@ -1887,13 +1890,13 @@ function lintArticle(article) {
|
|
|
1887
1890
|
async function walkMarkdown(dir) {
|
|
1888
1891
|
let entries;
|
|
1889
1892
|
try {
|
|
1890
|
-
entries = await
|
|
1893
|
+
entries = await fs12.readdir(dir, { withFileTypes: true });
|
|
1891
1894
|
} catch {
|
|
1892
1895
|
return [];
|
|
1893
1896
|
}
|
|
1894
1897
|
const out = [];
|
|
1895
1898
|
for (const e of entries) {
|
|
1896
|
-
const full =
|
|
1899
|
+
const full = path11.join(dir, e.name);
|
|
1897
1900
|
if (e.isDirectory()) out.push(...await walkMarkdown(full));
|
|
1898
1901
|
else if (e.isFile() && /\.mdx?$/.test(e.name) && !/-template\.mdx?$/.test(e.name)) out.push(full);
|
|
1899
1902
|
}
|
|
@@ -1923,11 +1926,11 @@ async function lintCollections(scope) {
|
|
|
1923
1926
|
let warnCount = 0;
|
|
1924
1927
|
for (const coll of collections) {
|
|
1925
1928
|
for (const full of await walkMarkdown(coll.path)) {
|
|
1926
|
-
const rel =
|
|
1929
|
+
const rel = path11.relative(coll.path, full);
|
|
1927
1930
|
const label = `${coll.name}/${rel}`;
|
|
1928
1931
|
let findings = [];
|
|
1929
1932
|
try {
|
|
1930
|
-
const parsed = parseArticle(await
|
|
1933
|
+
const parsed = parseArticle(await fs12.readFile(full, "utf8"));
|
|
1931
1934
|
findings = lintArticle(parsed);
|
|
1932
1935
|
} catch (err) {
|
|
1933
1936
|
findings = [{ level: "error", rule: "parse", message: `could not parse: ${err.message}` }];
|
|
@@ -2040,11 +2043,14 @@ export {
|
|
|
2040
2043
|
listEnabledPlugins,
|
|
2041
2044
|
listPlugins,
|
|
2042
2045
|
listSearchModes,
|
|
2046
|
+
listTemplateShapes,
|
|
2047
|
+
listTemplates,
|
|
2043
2048
|
loadSearchProvider,
|
|
2044
2049
|
localGraph,
|
|
2045
2050
|
nativeSearch,
|
|
2046
2051
|
parseAddTarget,
|
|
2047
2052
|
parseArticle,
|
|
2053
|
+
parseFrontmatter,
|
|
2048
2054
|
parsePathTarget,
|
|
2049
2055
|
pluginsDir,
|
|
2050
2056
|
pluginsRegistryPath,
|
|
@@ -2066,6 +2072,7 @@ export {
|
|
|
2066
2072
|
removePlugin,
|
|
2067
2073
|
removeTags,
|
|
2068
2074
|
renderArticle,
|
|
2075
|
+
repairPlugins,
|
|
2069
2076
|
repoId,
|
|
2070
2077
|
repoRemoteName,
|
|
2071
2078
|
resolveCollectionSelection,
|
|
@@ -2079,13 +2086,17 @@ export {
|
|
|
2079
2086
|
resolveProjectCollections,
|
|
2080
2087
|
search,
|
|
2081
2088
|
seedProjectHome,
|
|
2089
|
+
seedTemplates,
|
|
2082
2090
|
serializeArticle,
|
|
2083
2091
|
setArticleMeta,
|
|
2084
2092
|
setConfigValue,
|
|
2093
|
+
setContextCollections,
|
|
2085
2094
|
setTags,
|
|
2086
2095
|
showSystemEnabled,
|
|
2087
2096
|
slugify,
|
|
2088
2097
|
slugifyPath,
|
|
2098
|
+
stripFrontmatter,
|
|
2099
|
+
templatesDir,
|
|
2089
2100
|
toTreeNodes,
|
|
2090
2101
|
tokenize,
|
|
2091
2102
|
unregisterCollection,
|