diffwiki-core 0.4.0-rc.202607221918.a124bf3 → 0.4.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/index.cjs +167 -1147
- package/dist/index.d.cts +14 -389
- package/dist/index.d.ts +14 -389
- package/dist/index.js +187 -608
- package/package.json +1 -7
- package/dist/chunk-AZ6SZH6P.js +0 -484
- package/dist/chunk-EEETAETA.js +0 -145
- package/dist/events-H3D7FT4O.js +0 -6
- package/dist/plugin.cjs +0 -175
- package/dist/plugin.d.cts +0 -229
- package/dist/plugin.d.ts +0 -229
- package/dist/plugin.js +0 -14
package/dist/index.cjs
CHANGED
|
@@ -5,9 +5,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __esm = (fn, res) => function __init() {
|
|
9
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
-
};
|
|
11
8
|
var __export = (target, all) => {
|
|
12
9
|
for (var name in all)
|
|
13
10
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -30,57 +27,91 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
27
|
));
|
|
31
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
29
|
|
|
33
|
-
// src/
|
|
34
|
-
var
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
ArticleNotFoundError: () => ArticleNotFoundError,
|
|
34
|
+
CollectionExistsError: () => CollectionExistsError,
|
|
35
|
+
CollectionNotFoundError: () => CollectionNotFoundError,
|
|
36
|
+
DiffwikiError: () => DiffwikiError,
|
|
37
|
+
InvalidTargetError: () => InvalidTargetError,
|
|
38
|
+
NoDefaultCollectionError: () => NoDefaultCollectionError,
|
|
39
|
+
REPO_CONFIG_FILE: () => REPO_CONFIG_FILE,
|
|
40
|
+
addTags: () => addTags,
|
|
41
|
+
buildArticleTree: () => buildArticleTree,
|
|
42
|
+
collectionDir: () => collectionDir,
|
|
43
|
+
collectionsDir: () => collectionsDir,
|
|
44
|
+
configPath: () => configPath,
|
|
45
|
+
createArticle: () => createArticle,
|
|
46
|
+
createCollection: () => createCollection,
|
|
47
|
+
doctor: () => doctor,
|
|
48
|
+
findCollection: () => findCollection,
|
|
49
|
+
getConfigValue: () => getConfigValue,
|
|
50
|
+
initRepoWiki: () => initRepoWiki,
|
|
51
|
+
listArticleTree: () => listArticleTree,
|
|
52
|
+
listCollections: () => listCollections,
|
|
53
|
+
parseAddTarget: () => parseAddTarget,
|
|
54
|
+
parseArticle: () => parseArticle,
|
|
55
|
+
parsePathTarget: () => parsePathTarget,
|
|
56
|
+
query: () => query,
|
|
57
|
+
readArticle: () => readArticle,
|
|
58
|
+
readConfig: () => readConfig,
|
|
59
|
+
readRegistry: () => readRegistry,
|
|
60
|
+
readRepoConfig: () => readRepoConfig,
|
|
61
|
+
registerCollection: () => registerCollection,
|
|
62
|
+
registryPath: () => registryPath,
|
|
63
|
+
removeArticle: () => removeArticle,
|
|
64
|
+
removeCollection: () => removeCollection,
|
|
65
|
+
removeTags: () => removeTags,
|
|
66
|
+
renderArticle: () => renderArticle,
|
|
67
|
+
resolveHome: () => resolveHome,
|
|
68
|
+
serializeArticle: () => serializeArticle,
|
|
69
|
+
setConfigValue: () => setConfigValue,
|
|
70
|
+
setTags: () => setTags,
|
|
71
|
+
slugify: () => slugify,
|
|
72
|
+
unregisterCollection: () => unregisterCollection,
|
|
73
|
+
updateArticleBody: () => updateArticleBody,
|
|
74
|
+
writeConfig: () => writeConfig,
|
|
75
|
+
writeRegistry: () => writeRegistry
|
|
40
76
|
});
|
|
77
|
+
module.exports = __toCommonJS(index_exports);
|
|
41
78
|
|
|
42
79
|
// src/errors.ts
|
|
43
|
-
var DiffwikiError
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
DiffwikiError = class extends Error {
|
|
48
|
-
constructor(message) {
|
|
49
|
-
super(message);
|
|
50
|
-
this.name = new.target.name;
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
CollectionExistsError = class extends DiffwikiError {
|
|
54
|
-
constructor(name) {
|
|
55
|
-
super(`collection "${name}" already exists`);
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
CollectionNotFoundError = class extends DiffwikiError {
|
|
59
|
-
constructor(name) {
|
|
60
|
-
super(`collection "${name}" not found`);
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
NoDefaultCollectionError = class extends DiffwikiError {
|
|
64
|
-
constructor() {
|
|
65
|
-
super("no default collection set (use: diffwiki config-set defaultCollection <name>)");
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
ArticleNotFoundError = class extends DiffwikiError {
|
|
69
|
-
constructor(target) {
|
|
70
|
-
super(`article "${target}" not found`);
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
InvalidTargetError = class extends DiffwikiError {
|
|
74
|
-
constructor(target, expected) {
|
|
75
|
-
super(`invalid target "${target}" \u2014 ${expected}`);
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
PluginError = class extends DiffwikiError {
|
|
79
|
-
};
|
|
80
|
+
var DiffwikiError = class extends Error {
|
|
81
|
+
constructor(message) {
|
|
82
|
+
super(message);
|
|
83
|
+
this.name = new.target.name;
|
|
80
84
|
}
|
|
81
|
-
}
|
|
85
|
+
};
|
|
86
|
+
var CollectionExistsError = class extends DiffwikiError {
|
|
87
|
+
constructor(name) {
|
|
88
|
+
super(`collection "${name}" already exists`);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
var CollectionNotFoundError = class extends DiffwikiError {
|
|
92
|
+
constructor(name) {
|
|
93
|
+
super(`collection "${name}" not found`);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
var NoDefaultCollectionError = class extends DiffwikiError {
|
|
97
|
+
constructor() {
|
|
98
|
+
super("no default collection set (use: diffwiki config-set defaultCollection <name>)");
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
var ArticleNotFoundError = class extends DiffwikiError {
|
|
102
|
+
constructor(target) {
|
|
103
|
+
super(`article "${target}" not found`);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
var InvalidTargetError = class extends DiffwikiError {
|
|
107
|
+
constructor(target, expected) {
|
|
108
|
+
super(`invalid target "${target}" \u2014 ${expected}`);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
82
111
|
|
|
83
112
|
// src/paths.ts
|
|
113
|
+
var import_node_os = __toESM(require("os"), 1);
|
|
114
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
84
115
|
function resolveHome() {
|
|
85
116
|
const override = process.env.DIFFWIKI_HOME;
|
|
86
117
|
return override && override.length > 0 ? override : import_node_path.default.join(import_node_os.default.homedir(), ".diffwiki");
|
|
@@ -97,22 +128,15 @@ function collectionsDir() {
|
|
|
97
128
|
function collectionDir(name) {
|
|
98
129
|
return import_node_path.default.join(collectionsDir(), name);
|
|
99
130
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
var import_node_os, import_node_path;
|
|
107
|
-
var init_paths = __esm({
|
|
108
|
-
"src/paths.ts"() {
|
|
109
|
-
"use strict";
|
|
110
|
-
import_node_os = __toESM(require("os"), 1);
|
|
111
|
-
import_node_path = __toESM(require("path"), 1);
|
|
112
|
-
}
|
|
113
|
-
});
|
|
131
|
+
|
|
132
|
+
// src/config.ts
|
|
133
|
+
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
134
|
+
|
|
135
|
+
// src/collections.ts
|
|
136
|
+
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
114
137
|
|
|
115
138
|
// src/registry.ts
|
|
139
|
+
var import_promises = __toESM(require("fs/promises"), 1);
|
|
116
140
|
function isMissing(err) {
|
|
117
141
|
return err?.code === "ENOENT";
|
|
118
142
|
}
|
|
@@ -145,359 +169,8 @@ async function unregisterCollection(name) {
|
|
|
145
169
|
registry.collections = registry.collections.filter((c) => c.name !== name);
|
|
146
170
|
await writeRegistry(registry);
|
|
147
171
|
}
|
|
148
|
-
var import_promises;
|
|
149
|
-
var init_registry = __esm({
|
|
150
|
-
"src/registry.ts"() {
|
|
151
|
-
"use strict";
|
|
152
|
-
import_promises = __toESM(require("fs/promises"), 1);
|
|
153
|
-
init_paths();
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
// src/plugins/registry.ts
|
|
158
|
-
function isMissing2(err) {
|
|
159
|
-
return err?.code === "ENOENT";
|
|
160
|
-
}
|
|
161
|
-
async function readPluginRegistry() {
|
|
162
|
-
try {
|
|
163
|
-
const parsed = JSON.parse(await import_promises2.default.readFile(pluginsRegistryPath(), "utf8"));
|
|
164
|
-
return { version: parsed.version ?? 1, plugins: parsed.plugins ?? [] };
|
|
165
|
-
} catch (err) {
|
|
166
|
-
if (isMissing2(err)) return { version: 1, plugins: [] };
|
|
167
|
-
throw err;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
async function writePluginRegistry(registry) {
|
|
171
|
-
await import_promises2.default.mkdir(resolveHome(), { recursive: true });
|
|
172
|
-
await import_promises2.default.writeFile(pluginsRegistryPath(), `${JSON.stringify(registry, null, 2)}
|
|
173
|
-
`, "utf8");
|
|
174
|
-
}
|
|
175
|
-
async function upsertPlugin(record) {
|
|
176
|
-
const registry = await readPluginRegistry();
|
|
177
|
-
const index = registry.plugins.findIndex((p) => p.name === record.name);
|
|
178
|
-
if (index >= 0) {
|
|
179
|
-
registry.plugins[index] = record;
|
|
180
|
-
} else {
|
|
181
|
-
registry.plugins.push(record);
|
|
182
|
-
}
|
|
183
|
-
await writePluginRegistry(registry);
|
|
184
|
-
}
|
|
185
|
-
async function deregisterPlugin(name) {
|
|
186
|
-
const registry = await readPluginRegistry();
|
|
187
|
-
registry.plugins = registry.plugins.filter((p) => p.name !== name);
|
|
188
|
-
await writePluginRegistry(registry);
|
|
189
|
-
}
|
|
190
|
-
async function listEnabledPlugins(kind) {
|
|
191
|
-
const registry = await readPluginRegistry();
|
|
192
|
-
return registry.plugins.filter((p) => p.enabled && p.kind === kind);
|
|
193
|
-
}
|
|
194
|
-
async function findEnabledPlugin(kind, name) {
|
|
195
|
-
const enabled = await listEnabledPlugins(kind);
|
|
196
|
-
if (name) return enabled.find((p) => p.name === name);
|
|
197
|
-
return enabled[0];
|
|
198
|
-
}
|
|
199
|
-
async function findPluginRecord(name) {
|
|
200
|
-
const registry = await readPluginRegistry();
|
|
201
|
-
return registry.plugins.find((p) => p.name === name);
|
|
202
|
-
}
|
|
203
|
-
var import_promises2;
|
|
204
|
-
var init_registry2 = __esm({
|
|
205
|
-
"src/plugins/registry.ts"() {
|
|
206
|
-
"use strict";
|
|
207
|
-
import_promises2 = __toESM(require("fs/promises"), 1);
|
|
208
|
-
init_paths();
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
// src/logger.ts
|
|
213
|
-
function getLogger(scope) {
|
|
214
|
-
const parts = Array.isArray(scope) ? scope : [scope];
|
|
215
|
-
return (0, import_logtape.getLogger)(["diffwiki", ...parts]);
|
|
216
|
-
}
|
|
217
|
-
function resolveLogLevel(env = process.env) {
|
|
218
|
-
const raw = (env.DIFFWIKI_LOG ?? "").toLowerCase();
|
|
219
|
-
return LEVELS.includes(raw) ? raw : DEFAULT_LEVEL;
|
|
220
|
-
}
|
|
221
|
-
var import_logtape, LEVELS, DEFAULT_LEVEL;
|
|
222
|
-
var init_logger = __esm({
|
|
223
|
-
"src/logger.ts"() {
|
|
224
|
-
"use strict";
|
|
225
|
-
import_logtape = require("@logtape/logtape");
|
|
226
|
-
LEVELS = ["trace", "debug", "info", "warning", "error", "fatal"];
|
|
227
|
-
DEFAULT_LEVEL = "warning";
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
// src/plugins/host.ts
|
|
232
|
-
async function invokePlugin(command, op, params, opts) {
|
|
233
|
-
const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
234
|
-
const scope = opts?.name ?? (0, import_node_path2.basename)(command[1] ?? command[0] ?? "plugin");
|
|
235
|
-
return new Promise((resolve, reject) => {
|
|
236
|
-
const child = (0, import_node_child_process.spawn)(command[0], command.slice(1), {
|
|
237
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
238
|
-
});
|
|
239
|
-
let stdout = "";
|
|
240
|
-
let stderr = "";
|
|
241
|
-
let stderrLine = "";
|
|
242
|
-
let settled = false;
|
|
243
|
-
const forwardLog = (line) => {
|
|
244
|
-
const trimmedLine = line.trim();
|
|
245
|
-
if (!trimmedLine.startsWith("{")) return;
|
|
246
|
-
let parsed;
|
|
247
|
-
try {
|
|
248
|
-
parsed = JSON.parse(trimmedLine);
|
|
249
|
-
} catch {
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
const entry = parsed.log;
|
|
253
|
-
if (!entry || typeof entry.message !== "string") return;
|
|
254
|
-
const rawLevel = typeof entry.level === "string" ? entry.level.toLowerCase() : "";
|
|
255
|
-
const method = LEVEL_METHOD[rawLevel] ?? "info";
|
|
256
|
-
getLogger(["plugin", scope])[method](entry.message);
|
|
257
|
-
};
|
|
258
|
-
const timer = setTimeout(() => {
|
|
259
|
-
if (settled) return;
|
|
260
|
-
settled = true;
|
|
261
|
-
child.kill("SIGKILL");
|
|
262
|
-
reject(new Error(`plugin "${op}" timed out after ${timeoutMs}ms`));
|
|
263
|
-
}, timeoutMs);
|
|
264
|
-
const settle = (fn) => {
|
|
265
|
-
if (settled) return;
|
|
266
|
-
settled = true;
|
|
267
|
-
clearTimeout(timer);
|
|
268
|
-
fn();
|
|
269
|
-
};
|
|
270
|
-
child.stdout.setEncoding("utf8");
|
|
271
|
-
child.stdout.on("data", (chunk) => {
|
|
272
|
-
stdout += chunk;
|
|
273
|
-
});
|
|
274
|
-
child.stderr.setEncoding("utf8");
|
|
275
|
-
child.stderr.on("data", (chunk) => {
|
|
276
|
-
stderr += chunk;
|
|
277
|
-
stderrLine += chunk;
|
|
278
|
-
let nl;
|
|
279
|
-
while ((nl = stderrLine.indexOf("\n")) !== -1) {
|
|
280
|
-
forwardLog(stderrLine.slice(0, nl));
|
|
281
|
-
stderrLine = stderrLine.slice(nl + 1);
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
|
-
child.on("error", (err) => {
|
|
285
|
-
settle(() => reject(new Error(`plugin spawn failed: ${err.message}`)));
|
|
286
|
-
});
|
|
287
|
-
child.on("close", (code) => {
|
|
288
|
-
if (stderrLine.length > 0) {
|
|
289
|
-
forwardLog(stderrLine);
|
|
290
|
-
stderrLine = "";
|
|
291
|
-
}
|
|
292
|
-
settle(() => {
|
|
293
|
-
const trimmed = stdout.trim();
|
|
294
|
-
if (!trimmed) {
|
|
295
|
-
reject(
|
|
296
|
-
new Error(`plugin "${op}" produced no output (exit ${code})${stderr ? ": " + stderr.slice(0, 500) : ""}`)
|
|
297
|
-
);
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
let response;
|
|
301
|
-
try {
|
|
302
|
-
response = JSON.parse(trimmed);
|
|
303
|
-
} catch {
|
|
304
|
-
reject(new Error(`plugin "${op}" returned invalid JSON: ${trimmed.slice(0, 200)}`));
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
if (response.ok) {
|
|
308
|
-
resolve(response.result);
|
|
309
|
-
} else {
|
|
310
|
-
const message = typeof response.error?.message === "string" ? response.error.message : `plugin "${op}" failed`;
|
|
311
|
-
reject(new Error(message));
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
});
|
|
315
|
-
const request = JSON.stringify({ op, params });
|
|
316
|
-
child.stdin.write(request + "\n", (err) => {
|
|
317
|
-
if (err && !settled) {
|
|
318
|
-
settle(() => reject(new Error(`failed to write to plugin stdin: ${err.message}`)));
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
child.stdin.end();
|
|
322
|
-
});
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
function toWireCollections(collections) {
|
|
326
|
-
return collections.map((c) => ({ name: c.name, path: c.path }));
|
|
327
|
-
}
|
|
328
|
-
function mapHits(result, collections) {
|
|
329
|
-
const raw = result?.hits;
|
|
330
|
-
const hits = Array.isArray(raw) ? raw : [];
|
|
331
|
-
const out = [];
|
|
332
|
-
for (const item of hits) {
|
|
333
|
-
if (typeof item !== "object" || item === null) continue;
|
|
334
|
-
const h = item;
|
|
335
|
-
if (typeof h.collection !== "string" || typeof h.relPath !== "string") continue;
|
|
336
|
-
const title = typeof h.title === "string" ? h.title : h.relPath;
|
|
337
|
-
const coll = collections.find((c) => c.name === h.collection);
|
|
338
|
-
const abs = coll ? import_node_path2.default.join(coll.path, h.relPath) : h.relPath;
|
|
339
|
-
out.push({
|
|
340
|
-
collection: h.collection,
|
|
341
|
-
path: abs,
|
|
342
|
-
title,
|
|
343
|
-
tags: Array.isArray(h.tags) ? h.tags.filter((t) => typeof t === "string") : [],
|
|
344
|
-
snippet: typeof h.snippet === "string" ? h.snippet : void 0,
|
|
345
|
-
score: typeof h.score === "number" ? h.score : void 0,
|
|
346
|
-
docid: typeof h.docid === "string" ? h.docid : void 0
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
return out;
|
|
350
|
-
}
|
|
351
|
-
async function loadSearchProvider(name) {
|
|
352
|
-
const record = await findEnabledPlugin("search", name);
|
|
353
|
-
if (!record) return null;
|
|
354
|
-
let capabilities;
|
|
355
|
-
try {
|
|
356
|
-
const desc = await invokePlugin(record.command, "describe", {}, { name: record.name });
|
|
357
|
-
if (desc.kind !== "search") {
|
|
358
|
-
return null;
|
|
359
|
-
}
|
|
360
|
-
capabilities = desc.capabilities;
|
|
361
|
-
} catch {
|
|
362
|
-
return null;
|
|
363
|
-
}
|
|
364
|
-
const provider = {
|
|
365
|
-
name: record.name,
|
|
366
|
-
capabilities,
|
|
367
|
-
async handleSearch(term, collections, opts) {
|
|
368
|
-
const result = await invokePlugin(
|
|
369
|
-
record.command,
|
|
370
|
-
"search",
|
|
371
|
-
{
|
|
372
|
-
term,
|
|
373
|
-
collections: toWireCollections(collections),
|
|
374
|
-
collection: opts?.collection,
|
|
375
|
-
type: opts?.type,
|
|
376
|
-
limit: opts?.limit
|
|
377
|
-
},
|
|
378
|
-
{ timeoutMs: SEARCH_TIMEOUT_MS, name: record.name }
|
|
379
|
-
);
|
|
380
|
-
return mapHits(result, collections);
|
|
381
|
-
},
|
|
382
|
-
async reindex(collections, opts) {
|
|
383
|
-
const result = await invokePlugin(
|
|
384
|
-
record.command,
|
|
385
|
-
"index",
|
|
386
|
-
{
|
|
387
|
-
collections: toWireCollections(collections),
|
|
388
|
-
embed: opts?.embed
|
|
389
|
-
},
|
|
390
|
-
{ timeoutMs: LONG_TIMEOUT_MS, name: record.name }
|
|
391
|
-
);
|
|
392
|
-
return result?.indexed ?? 0;
|
|
393
|
-
},
|
|
394
|
-
async emitEvent(event, collection, relPath, collections) {
|
|
395
|
-
try {
|
|
396
|
-
const entry = collections.find((c) => c.name === collection);
|
|
397
|
-
await invokePlugin(
|
|
398
|
-
record.command,
|
|
399
|
-
"event",
|
|
400
|
-
{
|
|
401
|
-
event,
|
|
402
|
-
collection: entry ? { name: entry.name, path: entry.path } : { name: collection, path: "" },
|
|
403
|
-
relPath,
|
|
404
|
-
collections: toWireCollections(collections)
|
|
405
|
-
},
|
|
406
|
-
{ name: record.name }
|
|
407
|
-
);
|
|
408
|
-
} catch {
|
|
409
|
-
}
|
|
410
|
-
},
|
|
411
|
-
async health(collections) {
|
|
412
|
-
const result = await invokePlugin(record.command, "health", { collections: toWireCollections(collections) }, { name: record.name });
|
|
413
|
-
return {
|
|
414
|
-
ready: result?.ready === true,
|
|
415
|
-
readiness: result?.readiness,
|
|
416
|
-
diagnostics: result?.diagnostics ?? []
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
};
|
|
420
|
-
return provider;
|
|
421
|
-
}
|
|
422
|
-
async function listSearchModes() {
|
|
423
|
-
const modes = [{ plugin: NATIVE_ENGINE, type: "basic", label: "native \xB7 bm25" }];
|
|
424
|
-
const plugins = await listEnabledPlugins("search");
|
|
425
|
-
for (const record of plugins) {
|
|
426
|
-
const caps = record.capabilities;
|
|
427
|
-
if (!caps) continue;
|
|
428
|
-
for (const type of caps.searchTypes) {
|
|
429
|
-
modes.push({ plugin: record.name, type, label: `${record.name} \xB7 ${type}` });
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
return modes;
|
|
433
|
-
}
|
|
434
|
-
var import_node_child_process, import_node_path2, LEVEL_METHOD, DEFAULT_TIMEOUT_MS, SEARCH_TIMEOUT_MS, LONG_TIMEOUT_MS;
|
|
435
|
-
var init_host = __esm({
|
|
436
|
-
"src/plugins/host.ts"() {
|
|
437
|
-
"use strict";
|
|
438
|
-
import_node_child_process = require("child_process");
|
|
439
|
-
import_node_path2 = __toESM(require("path"), 1);
|
|
440
|
-
init_logger();
|
|
441
|
-
init_types();
|
|
442
|
-
init_registry2();
|
|
443
|
-
LEVEL_METHOD = {
|
|
444
|
-
trace: "trace",
|
|
445
|
-
debug: "debug",
|
|
446
|
-
info: "info",
|
|
447
|
-
warn: "warn",
|
|
448
|
-
warning: "warn",
|
|
449
|
-
error: "error",
|
|
450
|
-
fatal: "fatal"
|
|
451
|
-
};
|
|
452
|
-
DEFAULT_TIMEOUT_MS = 3e4;
|
|
453
|
-
SEARCH_TIMEOUT_MS = 12e4;
|
|
454
|
-
LONG_TIMEOUT_MS = 18e5;
|
|
455
|
-
}
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
// src/plugins/events.ts
|
|
459
|
-
var events_exports = {};
|
|
460
|
-
__export(events_exports, {
|
|
461
|
-
emitPluginEvent: () => emitPluginEvent
|
|
462
|
-
});
|
|
463
|
-
async function emitPluginEvent(event, collection, relPath) {
|
|
464
|
-
try {
|
|
465
|
-
const record = await findEnabledPlugin("search");
|
|
466
|
-
if (!record) return;
|
|
467
|
-
if (!record.capabilities?.events) return;
|
|
468
|
-
const collections = await listCollections();
|
|
469
|
-
const entry = collections.find((c) => c.name === collection);
|
|
470
|
-
await invokePlugin(
|
|
471
|
-
record.command,
|
|
472
|
-
"event",
|
|
473
|
-
{
|
|
474
|
-
event,
|
|
475
|
-
collection: entry ? { name: entry.name, path: entry.path } : { name: collection, path: "" },
|
|
476
|
-
relPath,
|
|
477
|
-
collections: collections.map((c) => ({ name: c.name, path: c.path }))
|
|
478
|
-
},
|
|
479
|
-
{ name: record.name }
|
|
480
|
-
);
|
|
481
|
-
} catch {
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
var init_events = __esm({
|
|
485
|
-
"src/plugins/events.ts"() {
|
|
486
|
-
"use strict";
|
|
487
|
-
init_collections();
|
|
488
|
-
init_registry2();
|
|
489
|
-
init_host();
|
|
490
|
-
}
|
|
491
|
-
});
|
|
492
172
|
|
|
493
173
|
// src/collections.ts
|
|
494
|
-
async function fireHook(event, collection) {
|
|
495
|
-
try {
|
|
496
|
-
const { emitPluginEvent: emitPluginEvent2 } = await Promise.resolve().then(() => (init_events(), events_exports));
|
|
497
|
-
await emitPluginEvent2(event, collection);
|
|
498
|
-
} catch {
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
174
|
async function listCollections() {
|
|
502
175
|
return (await readRegistry()).collections;
|
|
503
176
|
}
|
|
@@ -507,7 +180,7 @@ async function findCollection(name) {
|
|
|
507
180
|
async function createCollection(name) {
|
|
508
181
|
if (await findCollection(name)) throw new CollectionExistsError(name);
|
|
509
182
|
const dir = collectionDir(name);
|
|
510
|
-
await
|
|
183
|
+
await import_promises2.default.mkdir(dir, { recursive: true });
|
|
511
184
|
const entry = {
|
|
512
185
|
name,
|
|
513
186
|
type: "global",
|
|
@@ -515,7 +188,6 @@ async function createCollection(name) {
|
|
|
515
188
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
516
189
|
};
|
|
517
190
|
await registerCollection(entry);
|
|
518
|
-
void fireHook("collection-added", name);
|
|
519
191
|
return entry;
|
|
520
192
|
}
|
|
521
193
|
async function removeCollection(name) {
|
|
@@ -523,137 +195,32 @@ async function removeCollection(name) {
|
|
|
523
195
|
if (!entry) throw new CollectionNotFoundError(name);
|
|
524
196
|
await unregisterCollection(name);
|
|
525
197
|
if (entry.type === "global") {
|
|
526
|
-
await
|
|
198
|
+
await import_promises2.default.rm(entry.path, { recursive: true, force: true });
|
|
527
199
|
}
|
|
528
|
-
void fireHook("collection-removed", name);
|
|
529
200
|
return entry;
|
|
530
201
|
}
|
|
531
|
-
var import_promises3;
|
|
532
|
-
var init_collections = __esm({
|
|
533
|
-
"src/collections.ts"() {
|
|
534
|
-
"use strict";
|
|
535
|
-
import_promises3 = __toESM(require("fs/promises"), 1);
|
|
536
|
-
init_paths();
|
|
537
|
-
init_registry();
|
|
538
|
-
init_errors();
|
|
539
|
-
}
|
|
540
|
-
});
|
|
541
|
-
|
|
542
|
-
// src/index.ts
|
|
543
|
-
var index_exports = {};
|
|
544
|
-
__export(index_exports, {
|
|
545
|
-
ArticleNotFoundError: () => ArticleNotFoundError,
|
|
546
|
-
CollectionExistsError: () => CollectionExistsError,
|
|
547
|
-
CollectionNotFoundError: () => CollectionNotFoundError,
|
|
548
|
-
DiffwikiError: () => DiffwikiError,
|
|
549
|
-
InvalidTargetError: () => InvalidTargetError,
|
|
550
|
-
NATIVE_ENGINE: () => NATIVE_ENGINE,
|
|
551
|
-
NoDefaultCollectionError: () => NoDefaultCollectionError,
|
|
552
|
-
PluginError: () => PluginError,
|
|
553
|
-
REPO_CONFIG_FILE: () => REPO_CONFIG_FILE,
|
|
554
|
-
addTags: () => addTags,
|
|
555
|
-
buildArticleTree: () => buildArticleTree,
|
|
556
|
-
collectionDir: () => collectionDir,
|
|
557
|
-
collectionsDir: () => collectionsDir,
|
|
558
|
-
configPath: () => configPath,
|
|
559
|
-
createArticle: () => createArticle,
|
|
560
|
-
createCollection: () => createCollection,
|
|
561
|
-
deregisterPlugin: () => deregisterPlugin,
|
|
562
|
-
doctor: () => doctor,
|
|
563
|
-
emitPluginEvent: () => emitPluginEvent,
|
|
564
|
-
ensurePluginRoot: () => ensurePluginRoot,
|
|
565
|
-
findCollection: () => findCollection,
|
|
566
|
-
findEnabledPlugin: () => findEnabledPlugin,
|
|
567
|
-
findPluginRecord: () => findPluginRecord,
|
|
568
|
-
getConfigValue: () => getConfigValue,
|
|
569
|
-
getLogger: () => getLogger,
|
|
570
|
-
initRepoWiki: () => initRepoWiki,
|
|
571
|
-
installPlugin: () => installPlugin,
|
|
572
|
-
listArticleTree: () => listArticleTree,
|
|
573
|
-
listAvailablePlugins: () => listAvailablePlugins,
|
|
574
|
-
listCollections: () => listCollections,
|
|
575
|
-
listEnabledPlugins: () => listEnabledPlugins,
|
|
576
|
-
listPlugins: () => listPlugins,
|
|
577
|
-
listSearchModes: () => listSearchModes,
|
|
578
|
-
loadSearchProvider: () => loadSearchProvider,
|
|
579
|
-
nativeSearch: () => nativeSearch,
|
|
580
|
-
parseAddTarget: () => parseAddTarget,
|
|
581
|
-
parseArticle: () => parseArticle,
|
|
582
|
-
parsePathTarget: () => parsePathTarget,
|
|
583
|
-
pluginsDir: () => pluginsDir,
|
|
584
|
-
pluginsRegistryPath: () => pluginsRegistryPath,
|
|
585
|
-
query: () => query,
|
|
586
|
-
readArticle: () => readArticle,
|
|
587
|
-
readConfig: () => readConfig,
|
|
588
|
-
readPluginRegistry: () => readPluginRegistry,
|
|
589
|
-
readRegistry: () => readRegistry,
|
|
590
|
-
readRepoConfig: () => readRepoConfig,
|
|
591
|
-
registerCollection: () => registerCollection,
|
|
592
|
-
registerPlugin: () => registerPlugin,
|
|
593
|
-
registryPath: () => registryPath,
|
|
594
|
-
removeArticle: () => removeArticle,
|
|
595
|
-
removeCollection: () => removeCollection,
|
|
596
|
-
removePlugin: () => removePlugin,
|
|
597
|
-
removeTags: () => removeTags,
|
|
598
|
-
renderArticle: () => renderArticle,
|
|
599
|
-
resolveDefaultSearchMode: () => resolveDefaultSearchMode,
|
|
600
|
-
resolveHome: () => resolveHome,
|
|
601
|
-
resolveLogLevel: () => resolveLogLevel,
|
|
602
|
-
search: () => search,
|
|
603
|
-
serializeArticle: () => serializeArticle,
|
|
604
|
-
setConfigValue: () => setConfigValue,
|
|
605
|
-
setTags: () => setTags,
|
|
606
|
-
slugify: () => slugify,
|
|
607
|
-
unregisterCollection: () => unregisterCollection,
|
|
608
|
-
updateArticleBody: () => updateArticleBody,
|
|
609
|
-
upsertPlugin: () => upsertPlugin,
|
|
610
|
-
writeConfig: () => writeConfig,
|
|
611
|
-
writePluginRegistry: () => writePluginRegistry,
|
|
612
|
-
writeRegistry: () => writeRegistry
|
|
613
|
-
});
|
|
614
|
-
module.exports = __toCommonJS(index_exports);
|
|
615
|
-
init_types();
|
|
616
|
-
init_errors();
|
|
617
|
-
init_paths();
|
|
618
202
|
|
|
619
203
|
// src/config.ts
|
|
620
|
-
|
|
621
|
-
init_paths();
|
|
622
|
-
init_collections();
|
|
623
|
-
init_errors();
|
|
624
|
-
init_registry2();
|
|
625
|
-
init_types();
|
|
626
|
-
function isMissing3(err) {
|
|
204
|
+
function isMissing2(err) {
|
|
627
205
|
return err?.code === "ENOENT";
|
|
628
206
|
}
|
|
629
207
|
async function readConfig() {
|
|
630
208
|
try {
|
|
631
|
-
return JSON.parse(await
|
|
209
|
+
return JSON.parse(await import_promises3.default.readFile(configPath(), "utf8"));
|
|
632
210
|
} catch (err) {
|
|
633
|
-
if (
|
|
211
|
+
if (isMissing2(err)) return {};
|
|
634
212
|
throw err;
|
|
635
213
|
}
|
|
636
214
|
}
|
|
637
215
|
async function writeConfig(config) {
|
|
638
|
-
await
|
|
639
|
-
await
|
|
216
|
+
await import_promises3.default.mkdir(resolveHome(), { recursive: true });
|
|
217
|
+
await import_promises3.default.writeFile(configPath(), `${JSON.stringify(config, null, 2)}
|
|
640
218
|
`, "utf8");
|
|
641
219
|
}
|
|
642
220
|
async function setConfigValue(key, value) {
|
|
643
221
|
if (key === "defaultCollection" && !await findCollection(value)) {
|
|
644
222
|
throw new CollectionNotFoundError(value);
|
|
645
223
|
}
|
|
646
|
-
if (key === "defaultSearch") {
|
|
647
|
-
const engine = value.split(":")[0];
|
|
648
|
-
if (engine !== NATIVE_ENGINE) {
|
|
649
|
-
const enabled = await listEnabledPlugins("search");
|
|
650
|
-
if (!enabled.some((p) => p.name === engine)) {
|
|
651
|
-
console.warn(
|
|
652
|
-
`! defaultSearch "${value}" names an engine that is not installed/enabled \u2014 native will be used until it is`
|
|
653
|
-
);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
224
|
const config = await readConfig();
|
|
658
225
|
config[key] = value;
|
|
659
226
|
await writeConfig(config);
|
|
@@ -664,9 +231,6 @@ async function getConfigValue(key) {
|
|
|
664
231
|
return typeof value === "string" ? value : void 0;
|
|
665
232
|
}
|
|
666
233
|
|
|
667
|
-
// src/index.ts
|
|
668
|
-
init_registry();
|
|
669
|
-
|
|
670
234
|
// src/slugify.ts
|
|
671
235
|
function slugify(input) {
|
|
672
236
|
const slug = input.toLowerCase().replace(/^#+\s*/, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80).replace(/-+$/g, "");
|
|
@@ -684,33 +248,12 @@ var YAML_ENGINE = {
|
|
|
684
248
|
stringify: (data) => (0, import_yaml.stringify)(data)
|
|
685
249
|
};
|
|
686
250
|
var MATTER_OPTS = { engines: { yaml: YAML_ENGINE } };
|
|
687
|
-
function parseFallback(raw) {
|
|
688
|
-
const fenceRe = /^---[ \t]*\n([\s\S]*?)\n---[ \t]*\n/;
|
|
689
|
-
const fenceMatch = fenceRe.exec(raw);
|
|
690
|
-
let title = "";
|
|
691
|
-
let body;
|
|
692
|
-
if (fenceMatch) {
|
|
693
|
-
const inner = fenceMatch[1];
|
|
694
|
-
const titleMatch = /^title:\s*(.+)$/m.exec(inner);
|
|
695
|
-
if (titleMatch) {
|
|
696
|
-
title = titleMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
697
|
-
}
|
|
698
|
-
body = raw.slice(fenceMatch[0].length);
|
|
699
|
-
} else {
|
|
700
|
-
body = raw;
|
|
701
|
-
}
|
|
702
|
-
return { title, tags: [], body: body.replace(/^\n+/, "") };
|
|
703
|
-
}
|
|
704
251
|
function parseArticle(raw) {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
return { title, tags, created, body: content.replace(/^\n+/, "") };
|
|
711
|
-
} catch {
|
|
712
|
-
return parseFallback(raw);
|
|
713
|
-
}
|
|
252
|
+
const { data, content } = (0, import_gray_matter.default)(raw, MATTER_OPTS);
|
|
253
|
+
const title = typeof data.title === "string" ? data.title : "";
|
|
254
|
+
const tags = Array.isArray(data.tags) ? data.tags.map((t) => String(t)) : [];
|
|
255
|
+
const created = typeof data.created === "string" ? data.created : void 0;
|
|
256
|
+
return { title, tags, created, body: content.replace(/^\n+/, "") };
|
|
714
257
|
}
|
|
715
258
|
function serializeArticle(article) {
|
|
716
259
|
const front = { title: article.title, tags: article.tags };
|
|
@@ -719,21 +262,9 @@ function serializeArticle(article) {
|
|
|
719
262
|
return import_gray_matter.default.stringify(article.body, front, MATTER_OPTS);
|
|
720
263
|
}
|
|
721
264
|
|
|
722
|
-
// src/index.ts
|
|
723
|
-
init_collections();
|
|
724
|
-
|
|
725
265
|
// src/articles.ts
|
|
726
|
-
var
|
|
727
|
-
var
|
|
728
|
-
init_collections();
|
|
729
|
-
init_errors();
|
|
730
|
-
async function fireHook2(event, collection, relPath) {
|
|
731
|
-
try {
|
|
732
|
-
const { emitPluginEvent: emitPluginEvent2 } = await Promise.resolve().then(() => (init_events(), events_exports));
|
|
733
|
-
await emitPluginEvent2(event, collection, relPath);
|
|
734
|
-
} catch {
|
|
735
|
-
}
|
|
736
|
-
}
|
|
266
|
+
var import_promises4 = __toESM(require("fs/promises"), 1);
|
|
267
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
737
268
|
function parseAddTarget(target) {
|
|
738
269
|
const idx = target.indexOf(":");
|
|
739
270
|
if (idx === -1) {
|
|
@@ -766,38 +297,37 @@ async function resolveCollectionDir(name) {
|
|
|
766
297
|
}
|
|
767
298
|
function ensureMdPath(file) {
|
|
768
299
|
if (/\.mdx?$/.test(file)) return file;
|
|
769
|
-
const dir =
|
|
770
|
-
const base = slugify(
|
|
771
|
-
const rel = dir === "." ? `${base}.md` :
|
|
300
|
+
const dir = import_node_path2.default.dirname(file);
|
|
301
|
+
const base = slugify(import_node_path2.default.basename(file));
|
|
302
|
+
const rel = dir === "." ? `${base}.md` : import_node_path2.default.join(dir, `${base}.md`);
|
|
772
303
|
return rel;
|
|
773
304
|
}
|
|
774
305
|
async function createArticle(opts) {
|
|
775
306
|
const { collection, title } = parseAddTarget(opts.target);
|
|
776
307
|
const { name, dir } = await resolveCollectionDir(collection);
|
|
777
|
-
const filePath =
|
|
308
|
+
const filePath = import_node_path2.default.join(dir, `${slugify(title)}.md`);
|
|
778
309
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
779
310
|
const body = opts.body ?? `# ${title}
|
|
780
311
|
`;
|
|
781
|
-
await
|
|
782
|
-
await
|
|
783
|
-
void fireHook2("article-created", name, import_node_path3.default.relative(dir, filePath));
|
|
312
|
+
await import_promises4.default.mkdir(dir, { recursive: true });
|
|
313
|
+
await import_promises4.default.writeFile(filePath, serializeArticle({ title, tags: opts.tags, created: now, updated: now, body }), "utf8");
|
|
784
314
|
return { title, tags: opts.tags, path: filePath, collection: name };
|
|
785
315
|
}
|
|
786
316
|
async function loadArticle(target) {
|
|
787
317
|
const { collection, file } = parsePathTarget(target);
|
|
788
318
|
const { dir } = await resolveCollectionDir(collection);
|
|
789
|
-
const filePath =
|
|
319
|
+
const filePath = import_node_path2.default.join(dir, ensureMdPath(file));
|
|
790
320
|
try {
|
|
791
|
-
return { filePath,
|
|
321
|
+
return { filePath, raw: await import_promises4.default.readFile(filePath, "utf8"), collection };
|
|
792
322
|
} catch (err) {
|
|
793
323
|
if (err.code === "ENOENT") throw new ArticleNotFoundError(target);
|
|
794
324
|
throw err;
|
|
795
325
|
}
|
|
796
326
|
}
|
|
797
327
|
async function updateArticleBody(target, body) {
|
|
798
|
-
const { filePath,
|
|
328
|
+
const { filePath, raw, collection } = await loadArticle(target);
|
|
799
329
|
const parsed = parseArticle(raw);
|
|
800
|
-
await
|
|
330
|
+
await import_promises4.default.writeFile(
|
|
801
331
|
filePath,
|
|
802
332
|
serializeArticle({
|
|
803
333
|
title: parsed.title,
|
|
@@ -808,14 +338,13 @@ async function updateArticleBody(target, body) {
|
|
|
808
338
|
}),
|
|
809
339
|
"utf8"
|
|
810
340
|
);
|
|
811
|
-
void fireHook2("article-updated", collection, import_node_path3.default.relative(dir, filePath));
|
|
812
341
|
return { title: parsed.title, tags: parsed.tags, path: filePath, collection };
|
|
813
342
|
}
|
|
814
343
|
async function mutateTags(target, fn) {
|
|
815
|
-
const { filePath,
|
|
344
|
+
const { filePath, raw, collection } = await loadArticle(target);
|
|
816
345
|
const parsed = parseArticle(raw);
|
|
817
346
|
const tags = fn(parsed.tags);
|
|
818
|
-
await
|
|
347
|
+
await import_promises4.default.writeFile(
|
|
819
348
|
filePath,
|
|
820
349
|
serializeArticle({
|
|
821
350
|
title: parsed.title,
|
|
@@ -826,7 +355,6 @@ async function mutateTags(target, fn) {
|
|
|
826
355
|
}),
|
|
827
356
|
"utf8"
|
|
828
357
|
);
|
|
829
|
-
void fireHook2("article-updated", collection, import_node_path3.default.relative(dir, filePath));
|
|
830
358
|
return { title: parsed.title, tags, path: filePath, collection };
|
|
831
359
|
}
|
|
832
360
|
function addTags(target, tags) {
|
|
@@ -840,37 +368,34 @@ function setTags(target, tags) {
|
|
|
840
368
|
return mutateTags(target, () => Array.from(new Set(tags)));
|
|
841
369
|
}
|
|
842
370
|
async function removeArticle(target) {
|
|
843
|
-
const { filePath
|
|
844
|
-
await
|
|
845
|
-
void fireHook2("article-removed", collection, import_node_path3.default.relative(dir, filePath));
|
|
371
|
+
const { filePath } = await loadArticle(target);
|
|
372
|
+
await import_promises4.default.rm(filePath);
|
|
846
373
|
return filePath;
|
|
847
374
|
}
|
|
848
375
|
|
|
849
376
|
// src/repo.ts
|
|
850
|
-
var
|
|
851
|
-
var
|
|
852
|
-
var
|
|
377
|
+
var import_promises5 = __toESM(require("fs/promises"), 1);
|
|
378
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
379
|
+
var import_node_child_process = require("child_process");
|
|
853
380
|
var import_node_util = require("util");
|
|
854
381
|
var import_yaml2 = require("yaml");
|
|
855
|
-
|
|
856
|
-
init_registry();
|
|
857
|
-
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process2.execFile);
|
|
382
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
858
383
|
var REPO_CONFIG_FILE = "diffwiki.yaml";
|
|
859
384
|
async function detectRepoName(cwd) {
|
|
860
385
|
try {
|
|
861
386
|
const { stdout } = await execFileAsync("git", ["rev-parse", "--show-toplevel"], { cwd });
|
|
862
|
-
return
|
|
387
|
+
return import_node_path3.default.basename(stdout.trim());
|
|
863
388
|
} catch {
|
|
864
|
-
return
|
|
389
|
+
return import_node_path3.default.basename(import_node_path3.default.resolve(cwd));
|
|
865
390
|
}
|
|
866
391
|
}
|
|
867
392
|
async function readRepoConfig(cwd) {
|
|
868
393
|
try {
|
|
869
|
-
const raw = await
|
|
394
|
+
const raw = await import_promises5.default.readFile(import_node_path3.default.join(cwd, REPO_CONFIG_FILE), "utf8");
|
|
870
395
|
const parsed = (0, import_yaml2.parse)(raw) ?? {};
|
|
871
396
|
if (!parsed.path) return void 0;
|
|
872
397
|
return {
|
|
873
|
-
collection: parsed.collection ??
|
|
398
|
+
collection: parsed.collection ?? import_node_path3.default.basename(import_node_path3.default.resolve(cwd)),
|
|
874
399
|
path: parsed.path
|
|
875
400
|
};
|
|
876
401
|
} catch (err) {
|
|
@@ -879,9 +404,9 @@ async function readRepoConfig(cwd) {
|
|
|
879
404
|
}
|
|
880
405
|
}
|
|
881
406
|
async function initRepoWiki(opts) {
|
|
882
|
-
const cwd =
|
|
407
|
+
const cwd = import_node_path3.default.resolve(opts.cwd);
|
|
883
408
|
const wikiPath = opts.wikiPath ?? "wiki";
|
|
884
|
-
const absWiki =
|
|
409
|
+
const absWiki = import_node_path3.default.join(cwd, wikiPath);
|
|
885
410
|
let name = opts.collection ?? await detectRepoName(cwd);
|
|
886
411
|
const existing = await findCollection(name);
|
|
887
412
|
if (existing && existing.path !== absWiki) {
|
|
@@ -896,9 +421,9 @@ async function initRepoWiki(opts) {
|
|
|
896
421
|
n += 1;
|
|
897
422
|
}
|
|
898
423
|
}
|
|
899
|
-
await
|
|
424
|
+
await import_promises5.default.mkdir(absWiki, { recursive: true });
|
|
900
425
|
const config = { collection: name, path: wikiPath };
|
|
901
|
-
await
|
|
426
|
+
await import_promises5.default.writeFile(import_node_path3.default.join(cwd, REPO_CONFIG_FILE), (0, import_yaml2.stringify)(config), "utf8");
|
|
902
427
|
const entry = {
|
|
903
428
|
name,
|
|
904
429
|
type: "repo",
|
|
@@ -910,422 +435,44 @@ async function initRepoWiki(opts) {
|
|
|
910
435
|
}
|
|
911
436
|
|
|
912
437
|
// src/query.ts
|
|
913
|
-
var
|
|
914
|
-
var
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
init_logger();
|
|
918
|
-
init_host();
|
|
919
|
-
init_registry2();
|
|
920
|
-
init_types();
|
|
921
|
-
var log = getLogger("query");
|
|
922
|
-
async function collectMarkdownFiles(dir) {
|
|
923
|
-
let entries;
|
|
924
|
-
try {
|
|
925
|
-
entries = await import_promises7.default.readdir(dir, { withFileTypes: true });
|
|
926
|
-
} catch {
|
|
927
|
-
return [];
|
|
928
|
-
}
|
|
929
|
-
const files = [];
|
|
930
|
-
for (const entry of entries) {
|
|
931
|
-
const full = import_node_path5.default.join(dir, entry.name);
|
|
932
|
-
if (entry.isDirectory()) {
|
|
933
|
-
files.push(...await collectMarkdownFiles(full));
|
|
934
|
-
} else if (entry.isFile() && /\.mdx?$/.test(entry.name)) {
|
|
935
|
-
files.push(full);
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
return files;
|
|
939
|
-
}
|
|
940
|
-
var K1 = 1.5;
|
|
941
|
-
var B = 0.75;
|
|
942
|
-
var FIELD_WEIGHTS = { title: 3, tags: 2, collection: 1.5, body: 1 };
|
|
943
|
-
function tokenize(text) {
|
|
944
|
-
return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 0);
|
|
945
|
-
}
|
|
946
|
-
function termFreqs(tokens) {
|
|
947
|
-
const freqs = /* @__PURE__ */ new Map();
|
|
948
|
-
for (const t of tokens) {
|
|
949
|
-
freqs.set(t, (freqs.get(t) ?? 0) + 1);
|
|
950
|
-
}
|
|
951
|
-
return freqs;
|
|
952
|
-
}
|
|
953
|
-
function bm25FieldScore(queryTerms, fieldTokens, avgFieldLen, idf) {
|
|
954
|
-
const tf = termFreqs(fieldTokens);
|
|
955
|
-
const dl = fieldTokens.length;
|
|
956
|
-
const avgDl = avgFieldLen || 1;
|
|
957
|
-
let score = 0;
|
|
958
|
-
for (const term of queryTerms) {
|
|
959
|
-
const freq = tf.get(term) ?? 0;
|
|
960
|
-
if (freq === 0) continue;
|
|
961
|
-
const termIdf = idf.get(term) ?? 0;
|
|
962
|
-
const numerator = freq * (K1 + 1);
|
|
963
|
-
const denominator = freq + K1 * (1 - B + B * (dl / avgDl));
|
|
964
|
-
score += termIdf * (numerator / denominator);
|
|
965
|
-
}
|
|
966
|
-
return score;
|
|
967
|
-
}
|
|
968
|
-
async function nativeSearch(term, collections, collection) {
|
|
969
|
-
let targets = collections;
|
|
438
|
+
var import_promises6 = __toESM(require("fs/promises"), 1);
|
|
439
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
440
|
+
async function query(term, collection) {
|
|
441
|
+
let targets;
|
|
970
442
|
if (collection) {
|
|
971
|
-
const one =
|
|
443
|
+
const one = await findCollection(collection);
|
|
972
444
|
if (!one) throw new CollectionNotFoundError(collection);
|
|
973
445
|
targets = [one];
|
|
446
|
+
} else {
|
|
447
|
+
targets = await listCollections();
|
|
974
448
|
}
|
|
975
|
-
const
|
|
449
|
+
const needle = term.toLowerCase();
|
|
450
|
+
const hits = [];
|
|
976
451
|
for (const entry of targets) {
|
|
977
|
-
|
|
978
|
-
const fallbackTitle = import_node_path5.default.basename(full).replace(/\.mdx?$/, "");
|
|
979
|
-
let title = fallbackTitle;
|
|
980
|
-
let tags = [];
|
|
981
|
-
let body = "";
|
|
982
|
-
try {
|
|
983
|
-
const parsed = parseArticle(await import_promises7.default.readFile(full, "utf8"));
|
|
984
|
-
if (parsed.title) title = parsed.title;
|
|
985
|
-
tags = parsed.tags;
|
|
986
|
-
body = parsed.body;
|
|
987
|
-
} catch {
|
|
988
|
-
}
|
|
989
|
-
docs.push({
|
|
990
|
-
collectionName: entry.name,
|
|
991
|
-
filePath: full,
|
|
992
|
-
title,
|
|
993
|
-
tags,
|
|
994
|
-
fields: {
|
|
995
|
-
title: tokenize(title),
|
|
996
|
-
tags: tokenize(tags.join(" ")),
|
|
997
|
-
collection: tokenize(entry.name),
|
|
998
|
-
body: tokenize(body)
|
|
999
|
-
}
|
|
1000
|
-
});
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
if (!term.trim()) {
|
|
1004
|
-
return docs.map((d) => ({
|
|
1005
|
-
collection: d.collectionName,
|
|
1006
|
-
path: d.filePath,
|
|
1007
|
-
title: d.title,
|
|
1008
|
-
tags: d.tags
|
|
1009
|
-
}));
|
|
1010
|
-
}
|
|
1011
|
-
const queryTerms = tokenize(term);
|
|
1012
|
-
if (queryTerms.length === 0) {
|
|
1013
|
-
return docs.map((d) => ({
|
|
1014
|
-
collection: d.collectionName,
|
|
1015
|
-
path: d.filePath,
|
|
1016
|
-
title: d.title,
|
|
1017
|
-
tags: d.tags
|
|
1018
|
-
}));
|
|
1019
|
-
}
|
|
1020
|
-
const docTokenSets = docs.map(
|
|
1021
|
-
(doc) => /* @__PURE__ */ new Set([...doc.fields.title, ...doc.fields.tags, ...doc.fields.collection, ...doc.fields.body])
|
|
1022
|
-
);
|
|
1023
|
-
const N = docs.length;
|
|
1024
|
-
const idf = /* @__PURE__ */ new Map();
|
|
1025
|
-
for (const qt of queryTerms) {
|
|
1026
|
-
let docFreq = 0;
|
|
1027
|
-
for (const tokenSet of docTokenSets) {
|
|
1028
|
-
if (tokenSet.has(qt)) docFreq++;
|
|
1029
|
-
}
|
|
1030
|
-
idf.set(qt, Math.log((N - docFreq + 0.5) / (docFreq + 0.5) + 1));
|
|
1031
|
-
}
|
|
1032
|
-
const avgLens = {
|
|
1033
|
-
title: docs.reduce((sum, d) => sum + d.fields.title.length, 0) / (N || 1),
|
|
1034
|
-
tags: docs.reduce((sum, d) => sum + d.fields.tags.length, 0) / (N || 1),
|
|
1035
|
-
collection: docs.reduce((sum, d) => sum + d.fields.collection.length, 0) / (N || 1),
|
|
1036
|
-
body: docs.reduce((sum, d) => sum + d.fields.body.length, 0) / (N || 1)
|
|
1037
|
-
};
|
|
1038
|
-
const scored = [];
|
|
1039
|
-
for (const doc of docs) {
|
|
1040
|
-
const titleScore = bm25FieldScore(queryTerms, doc.fields.title, avgLens.title, idf) * FIELD_WEIGHTS.title;
|
|
1041
|
-
const tagsScore = bm25FieldScore(queryTerms, doc.fields.tags, avgLens.tags, idf) * FIELD_WEIGHTS.tags;
|
|
1042
|
-
const collScore = bm25FieldScore(queryTerms, doc.fields.collection, avgLens.collection, idf) * FIELD_WEIGHTS.collection;
|
|
1043
|
-
const bodyScore = bm25FieldScore(queryTerms, doc.fields.body, avgLens.body, idf) * FIELD_WEIGHTS.body;
|
|
1044
|
-
const total = titleScore + tagsScore + collScore + bodyScore;
|
|
1045
|
-
if (total > 0) {
|
|
1046
|
-
scored.push({ doc, score: total });
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
scored.sort((a, b) => b.score - a.score);
|
|
1050
|
-
return scored.map((s) => ({
|
|
1051
|
-
collection: s.doc.collectionName,
|
|
1052
|
-
path: s.doc.filePath,
|
|
1053
|
-
title: s.doc.title,
|
|
1054
|
-
tags: s.doc.tags,
|
|
1055
|
-
score: s.score
|
|
1056
|
-
}));
|
|
1057
|
-
}
|
|
1058
|
-
async function resolveDefaultEngine() {
|
|
1059
|
-
const config = await readConfig();
|
|
1060
|
-
const enabled = await listEnabledPlugins("search");
|
|
1061
|
-
const configured = config.defaultSearch?.split(":")[0];
|
|
1062
|
-
if (configured && enabled.some((p) => p.name === configured)) return configured;
|
|
1063
|
-
if (enabled.length > 0) return enabled[0].name;
|
|
1064
|
-
return NATIVE_ENGINE;
|
|
1065
|
-
}
|
|
1066
|
-
async function resolveDefaultSearchMode() {
|
|
1067
|
-
const config = await readConfig();
|
|
1068
|
-
const enabled = await listEnabledPlugins("search");
|
|
1069
|
-
const configured = config.defaultSearch?.split(":")[0];
|
|
1070
|
-
const configuredType = config.defaultSearch?.split(":")[1];
|
|
1071
|
-
let engine = NATIVE_ENGINE;
|
|
1072
|
-
if (configured && enabled.some((p) => p.name === configured)) engine = configured;
|
|
1073
|
-
else if (enabled.length > 0) engine = enabled[0].name;
|
|
1074
|
-
if (engine === NATIVE_ENGINE) return { engine, type: "basic" };
|
|
1075
|
-
const record = enabled.find((p) => p.name === engine);
|
|
1076
|
-
const searchTypes = record?.capabilities?.searchTypes ?? [];
|
|
1077
|
-
if (searchTypes.length === 0) return { engine: NATIVE_ENGINE, type: "basic" };
|
|
1078
|
-
if (configuredType && searchTypes.includes(configuredType)) {
|
|
1079
|
-
return { engine, type: configuredType };
|
|
1080
|
-
}
|
|
1081
|
-
return { engine, type: searchTypes[0] };
|
|
1082
|
-
}
|
|
1083
|
-
async function search(term, opts) {
|
|
1084
|
-
const collections = await listCollections();
|
|
1085
|
-
const engine = opts?.engine ?? await resolveDefaultEngine();
|
|
1086
|
-
log.debug(`resolved search engine "${engine}"`);
|
|
1087
|
-
if (engine === NATIVE_ENGINE) {
|
|
1088
|
-
const hits = await nativeSearch(term, collections, opts?.collection);
|
|
1089
|
-
return { hits, engine, fellBackToNative: false };
|
|
1090
|
-
}
|
|
1091
|
-
const provider = await loadSearchProvider(engine);
|
|
1092
|
-
if (!provider) {
|
|
1093
|
-
const error = `no provider found for engine "${engine}"`;
|
|
1094
|
-
log.warn(`engine "${engine}" unavailable, falling back to native: ${error}`);
|
|
1095
|
-
const hits = await nativeSearch(term, collections, opts?.collection);
|
|
1096
|
-
return { hits, engine, fellBackToNative: true, error };
|
|
1097
|
-
}
|
|
1098
|
-
try {
|
|
1099
|
-
const hits = await provider.handleSearch(term, collections, opts);
|
|
1100
|
-
return { hits, engine, fellBackToNative: false };
|
|
1101
|
-
} catch (err) {
|
|
1102
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1103
|
-
log.warn(`engine "${engine}" failed, falling back to native: ${message}`);
|
|
1104
|
-
const hits = await nativeSearch(term, collections, opts?.collection);
|
|
1105
|
-
return { hits, engine, fellBackToNative: true, error: message };
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
async function query(term, opts) {
|
|
1109
|
-
return (await search(term, opts)).hits;
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
// src/doctor.ts
|
|
1113
|
-
var import_promises9 = __toESM(require("fs/promises"), 1);
|
|
1114
|
-
init_paths();
|
|
1115
|
-
init_registry();
|
|
1116
|
-
init_collections();
|
|
1117
|
-
|
|
1118
|
-
// src/plugins/manager.ts
|
|
1119
|
-
var import_promises8 = __toESM(require("fs/promises"), 1);
|
|
1120
|
-
var import_node_path7 = __toESM(require("path"), 1);
|
|
1121
|
-
init_paths();
|
|
1122
|
-
init_errors();
|
|
1123
|
-
init_collections();
|
|
1124
|
-
init_registry2();
|
|
1125
|
-
init_host();
|
|
1126
|
-
|
|
1127
|
-
// src/plugins/sdk.ts
|
|
1128
|
-
var import_node_child_process3 = require("child_process");
|
|
1129
|
-
var import_node_util2 = require("util");
|
|
1130
|
-
var import_node_fs = require("fs");
|
|
1131
|
-
var import_node_path6 = require("path");
|
|
1132
|
-
var import_node_url = require("url");
|
|
1133
|
-
var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process3.execFile);
|
|
1134
|
-
async function runProcess(file, args, opts) {
|
|
1135
|
-
const { stdout, stderr } = await execFileAsync2(file, args, {
|
|
1136
|
-
env: opts?.env,
|
|
1137
|
-
timeout: opts?.timeout,
|
|
1138
|
-
// Large: qmd emits multi-MB JSON search payloads that overflow the default 1 MB.
|
|
1139
|
-
maxBuffer: 50 * 1024 * 1024
|
|
1140
|
-
});
|
|
1141
|
-
return { stdout, stderr };
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
// src/plugins/manager.ts
|
|
1145
|
-
async function readPackageJson(file) {
|
|
1146
|
-
try {
|
|
1147
|
-
return JSON.parse(await import_promises8.default.readFile(file, "utf8"));
|
|
1148
|
-
} catch {
|
|
1149
|
-
return null;
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
function binOf(manifest) {
|
|
1153
|
-
if (typeof manifest.bin === "string") return manifest.bin;
|
|
1154
|
-
if (manifest.bin && typeof manifest.bin === "object") {
|
|
1155
|
-
const first = Object.values(manifest.bin)[0];
|
|
1156
|
-
return typeof first === "string" ? first : null;
|
|
1157
|
-
}
|
|
1158
|
-
return null;
|
|
1159
|
-
}
|
|
1160
|
-
async function pluginManifestDirs(nodeModules) {
|
|
1161
|
-
let entries;
|
|
1162
|
-
try {
|
|
1163
|
-
entries = await import_promises8.default.readdir(nodeModules);
|
|
1164
|
-
} catch {
|
|
1165
|
-
return [];
|
|
1166
|
-
}
|
|
1167
|
-
const dirs = [];
|
|
1168
|
-
for (const entry of entries) {
|
|
1169
|
-
if (entry.startsWith(".")) continue;
|
|
1170
|
-
if (entry.startsWith("@")) {
|
|
1171
|
-
let scoped;
|
|
1172
|
-
try {
|
|
1173
|
-
scoped = await import_promises8.default.readdir(import_node_path7.default.join(nodeModules, entry));
|
|
1174
|
-
} catch {
|
|
1175
|
-
continue;
|
|
1176
|
-
}
|
|
1177
|
-
for (const pkg of scoped) {
|
|
1178
|
-
if (pkg.startsWith(".")) continue;
|
|
1179
|
-
dirs.push(import_node_path7.default.join(nodeModules, entry, pkg));
|
|
1180
|
-
}
|
|
1181
|
-
} else {
|
|
1182
|
-
dirs.push(import_node_path7.default.join(nodeModules, entry));
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
return dirs;
|
|
1186
|
-
}
|
|
1187
|
-
async function ensurePluginRoot() {
|
|
1188
|
-
const dir = pluginsDir();
|
|
1189
|
-
await import_promises8.default.mkdir(dir, { recursive: true });
|
|
1190
|
-
const pkgPath = import_node_path7.default.join(dir, "package.json");
|
|
1191
|
-
if (await readPackageJson(pkgPath) === null) {
|
|
1192
|
-
const pkg = { name: "diffwiki-plugins", private: true, version: "0.0.0" };
|
|
1193
|
-
await import_promises8.default.writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
1194
|
-
`, "utf8");
|
|
1195
|
-
}
|
|
1196
|
-
return dir;
|
|
1197
|
-
}
|
|
1198
|
-
async function discoverSearchPackage(dir) {
|
|
1199
|
-
const nodeModules = import_node_path7.default.join(dir, "node_modules");
|
|
1200
|
-
for (const pkgDir of await pluginManifestDirs(nodeModules)) {
|
|
1201
|
-
const manifest = await readPackageJson(import_node_path7.default.join(pkgDir, "package.json"));
|
|
1202
|
-
if (!manifest?.name || manifest.diffwiki?.kind !== "search") continue;
|
|
1203
|
-
const bin = binOf(manifest);
|
|
1204
|
-
if (!bin) continue;
|
|
1205
|
-
return { name: manifest.name, version: manifest.version, binAbs: import_node_path7.default.resolve(pkgDir, bin) };
|
|
1206
|
-
}
|
|
1207
|
-
return null;
|
|
1208
|
-
}
|
|
1209
|
-
async function describePlugin(command, name) {
|
|
1210
|
-
return invokePlugin(command, "describe", {}, { name });
|
|
1211
|
-
}
|
|
1212
|
-
async function runSetup(command, name) {
|
|
1213
|
-
try {
|
|
1214
|
-
const collections = await listCollections();
|
|
1215
|
-
const result = await invokePlugin(
|
|
1216
|
-
command,
|
|
1217
|
-
"setup",
|
|
1218
|
-
{
|
|
1219
|
-
collections: collections.map((c) => ({ name: c.name, path: c.path })),
|
|
1220
|
-
embed: false,
|
|
1221
|
-
prefetchModels: false
|
|
1222
|
-
},
|
|
1223
|
-
{ timeoutMs: LONG_TIMEOUT_MS, name }
|
|
1224
|
-
);
|
|
1225
|
-
return { ready: result?.ready === true, steps: result?.steps ?? [] };
|
|
1226
|
-
} catch (err) {
|
|
1227
|
-
return { ready: false, steps: [{ step: "setup", ok: false, message: err.message }] };
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
async function installPlugin(spec, opts) {
|
|
1231
|
-
const dir = await ensurePluginRoot();
|
|
1232
|
-
try {
|
|
1233
|
-
await runProcess("npm", ["install", spec, "--prefix", dir]);
|
|
1234
|
-
} catch (err) {
|
|
1235
|
-
throw new PluginError(`npm install ${spec} failed: ${err.message}`);
|
|
1236
|
-
}
|
|
1237
|
-
const discovered = await discoverSearchPackage(dir);
|
|
1238
|
-
if (!discovered) {
|
|
1239
|
-
throw new PluginError(
|
|
1240
|
-
`no diffwiki search plugin found in "${spec}" (need package.json diffwiki.kind === "search" + bin)`
|
|
1241
|
-
);
|
|
1242
|
-
}
|
|
1243
|
-
const command = ["node", discovered.binAbs];
|
|
1244
|
-
let capabilities;
|
|
1245
|
-
try {
|
|
1246
|
-
const desc = await describePlugin(command, discovered.name);
|
|
1247
|
-
if (desc.kind !== "search") {
|
|
1248
|
-
throw new PluginError(`plugin "${discovered.name}" reported kind "${desc.kind}", expected "search"`);
|
|
1249
|
-
}
|
|
1250
|
-
capabilities = desc.capabilities;
|
|
1251
|
-
} catch (err) {
|
|
1252
|
-
if (err instanceof PluginError) throw err;
|
|
1253
|
-
throw new PluginError(`plugin "${discovered.name}" failed describe: ${err.message}`);
|
|
1254
|
-
}
|
|
1255
|
-
const record = {
|
|
1256
|
-
name: discovered.name,
|
|
1257
|
-
kind: "search",
|
|
1258
|
-
command,
|
|
1259
|
-
version: discovered.version,
|
|
1260
|
-
capabilities,
|
|
1261
|
-
enabled: true,
|
|
1262
|
-
managed: true
|
|
1263
|
-
};
|
|
1264
|
-
await upsertPlugin(record);
|
|
1265
|
-
const setup = opts?.setup !== false ? await runSetup(command, discovered.name) : void 0;
|
|
1266
|
-
return { record, setup };
|
|
1267
|
-
}
|
|
1268
|
-
async function registerPlugin(name, command) {
|
|
1269
|
-
if (command.length === 0) throw new PluginError(`register "${name}": --command must not be empty`);
|
|
1270
|
-
let capabilities;
|
|
1271
|
-
try {
|
|
1272
|
-
const desc = await describePlugin(command, name);
|
|
1273
|
-
if (desc.kind !== "search") {
|
|
1274
|
-
throw new PluginError(`plugin "${name}" reported kind "${desc.kind}", expected "search"`);
|
|
1275
|
-
}
|
|
1276
|
-
capabilities = desc.capabilities;
|
|
1277
|
-
} catch (err) {
|
|
1278
|
-
if (err instanceof PluginError) throw err;
|
|
1279
|
-
throw new PluginError(`plugin "${name}" did not respond to describe (command: ${command.join(" ")})`);
|
|
1280
|
-
}
|
|
1281
|
-
const record = {
|
|
1282
|
-
name,
|
|
1283
|
-
kind: "search",
|
|
1284
|
-
command,
|
|
1285
|
-
capabilities,
|
|
1286
|
-
enabled: true,
|
|
1287
|
-
managed: false
|
|
1288
|
-
};
|
|
1289
|
-
await upsertPlugin(record);
|
|
1290
|
-
await runSetup(command, name);
|
|
1291
|
-
return record;
|
|
1292
|
-
}
|
|
1293
|
-
async function removePlugin(name) {
|
|
1294
|
-
const record = (await readPluginRegistry()).plugins.find((p) => p.name === name);
|
|
1295
|
-
if (!record) throw new PluginError(`plugin "${name}" is not registered`);
|
|
1296
|
-
if (record.managed) {
|
|
1297
|
-
const dir = await ensurePluginRoot();
|
|
452
|
+
let files;
|
|
1298
453
|
try {
|
|
1299
|
-
await
|
|
454
|
+
files = await import_promises6.default.readdir(entry.path);
|
|
1300
455
|
} catch {
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
for (const file of files) {
|
|
459
|
+
if (!/\.mdx?$/.test(file)) continue;
|
|
460
|
+
const full = import_node_path4.default.join(entry.path, file);
|
|
461
|
+
const parsed = parseArticle(await import_promises6.default.readFile(full, "utf8"));
|
|
462
|
+
const haystack = [parsed.title, file, ...parsed.tags].join(" ").toLowerCase();
|
|
463
|
+
if (!needle || haystack.includes(needle)) {
|
|
464
|
+
hits.push({ collection: entry.name, path: full, title: parsed.title, tags: parsed.tags });
|
|
465
|
+
}
|
|
1301
466
|
}
|
|
1302
467
|
}
|
|
1303
|
-
|
|
1304
|
-
}
|
|
1305
|
-
async function probeDescribable(command, name) {
|
|
1306
|
-
try {
|
|
1307
|
-
await describePlugin(command, name);
|
|
1308
|
-
return true;
|
|
1309
|
-
} catch {
|
|
1310
|
-
return false;
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
async function listPlugins() {
|
|
1314
|
-
const registry = await readPluginRegistry();
|
|
1315
|
-
const out = [];
|
|
1316
|
-
for (const record of registry.plugins) {
|
|
1317
|
-
const spawnable = record.enabled ? await probeDescribable(record.command, record.name) : false;
|
|
1318
|
-
out.push({ ...record, spawnable });
|
|
1319
|
-
}
|
|
1320
|
-
return out;
|
|
468
|
+
return hits;
|
|
1321
469
|
}
|
|
1322
470
|
|
|
1323
471
|
// src/doctor.ts
|
|
1324
|
-
|
|
1325
|
-
init_types();
|
|
472
|
+
var import_promises7 = __toESM(require("fs/promises"), 1);
|
|
1326
473
|
async function exists(target) {
|
|
1327
474
|
try {
|
|
1328
|
-
await
|
|
475
|
+
await import_promises7.default.access(target);
|
|
1329
476
|
return true;
|
|
1330
477
|
} catch {
|
|
1331
478
|
return false;
|
|
@@ -1358,86 +505,12 @@ async function doctor() {
|
|
|
1358
505
|
} else {
|
|
1359
506
|
out.push({ level: "warn", message: "no default collection set" });
|
|
1360
507
|
}
|
|
1361
|
-
await appendPluginDiagnostics(out, config);
|
|
1362
508
|
return out;
|
|
1363
509
|
}
|
|
1364
|
-
function readinessLine(name, health) {
|
|
1365
|
-
const r = health.readiness;
|
|
1366
|
-
if (!r) {
|
|
1367
|
-
return { level: health.ready ? "ok" : "warn", message: `plugin "${name}" ${health.ready ? "ready" : "not ready"}` };
|
|
1368
|
-
}
|
|
1369
|
-
if (r.firstSearchCost === "model-download") {
|
|
1370
|
-
return {
|
|
1371
|
-
level: "warn",
|
|
1372
|
-
message: `plugin "${name}": ${r.docsIndexed} doc(s) indexed \u2014 first search will download models (~2GB)`
|
|
1373
|
-
};
|
|
1374
|
-
}
|
|
1375
|
-
if (r.firstSearchCost === "model-load") {
|
|
1376
|
-
return {
|
|
1377
|
-
level: "warn",
|
|
1378
|
-
message: `plugin "${name}": ${r.docsIndexed} doc(s) indexed \u2014 first search loads models (a few seconds)`
|
|
1379
|
-
};
|
|
1380
|
-
}
|
|
1381
|
-
return { level: "ok", message: `plugin "${name}": ${r.docsIndexed} doc(s) indexed \u2014 first search is instant` };
|
|
1382
|
-
}
|
|
1383
|
-
async function appendPluginDiagnostics(out, config) {
|
|
1384
|
-
let plugins = [];
|
|
1385
|
-
try {
|
|
1386
|
-
plugins = await listPlugins();
|
|
1387
|
-
} catch (err) {
|
|
1388
|
-
out.push({ level: "error", message: `failed to read plugin registry: ${err.message}` });
|
|
1389
|
-
return;
|
|
1390
|
-
}
|
|
1391
|
-
if (plugins.length === 0) {
|
|
1392
|
-
out.push({ level: "warn", message: "no search plugins installed \u2014 using built-in native search" });
|
|
1393
|
-
}
|
|
1394
|
-
const collections = await listCollections();
|
|
1395
|
-
for (const p of plugins) {
|
|
1396
|
-
if (!p.enabled) {
|
|
1397
|
-
out.push({ level: "warn", message: `plugin "${p.name}" registered but disabled` });
|
|
1398
|
-
continue;
|
|
1399
|
-
}
|
|
1400
|
-
if (!p.spawnable) {
|
|
1401
|
-
out.push({
|
|
1402
|
-
level: "error",
|
|
1403
|
-
message: `plugin "${p.name}" registered but its command is not spawnable: ${p.command.join(" ")}`
|
|
1404
|
-
});
|
|
1405
|
-
continue;
|
|
1406
|
-
}
|
|
1407
|
-
out.push({ level: "ok", message: `plugin "${p.name}" enabled and spawnable` });
|
|
1408
|
-
try {
|
|
1409
|
-
const provider = await loadSearchProvider(p.name);
|
|
1410
|
-
if (!provider) {
|
|
1411
|
-
out.push({ level: "error", message: `plugin "${p.name}" failed to load for a health check` });
|
|
1412
|
-
continue;
|
|
1413
|
-
}
|
|
1414
|
-
const health = await provider.health(collections);
|
|
1415
|
-
for (const d of health.diagnostics) out.push({ level: d.level, message: `${p.name} \xB7 ${d.message}` });
|
|
1416
|
-
out.push(readinessLine(p.name, health));
|
|
1417
|
-
} catch (err) {
|
|
1418
|
-
out.push({ level: "error", message: `plugin "${p.name}" health check failed: ${err.message}` });
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
if (config.defaultSearch) {
|
|
1422
|
-
const engine = config.defaultSearch.split(":")[0];
|
|
1423
|
-
const known = engine === NATIVE_ENGINE || plugins.some((p) => p.name === engine && p.enabled);
|
|
1424
|
-
if (!known) {
|
|
1425
|
-
out.push({
|
|
1426
|
-
level: "warn",
|
|
1427
|
-
message: `defaultSearch "${config.defaultSearch}" is not installed/enabled \u2014 using native`
|
|
1428
|
-
});
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
// src/index.ts
|
|
1434
|
-
init_logger();
|
|
1435
510
|
|
|
1436
511
|
// src/browse.ts
|
|
1437
|
-
var
|
|
1438
|
-
var
|
|
1439
|
-
init_collections();
|
|
1440
|
-
init_errors();
|
|
512
|
+
var import_promises8 = __toESM(require("fs/promises"), 1);
|
|
513
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
1441
514
|
var INDEX_RE = /^(index|readme)\.mdx?$/i;
|
|
1442
515
|
function buildArticleTree(relPaths) {
|
|
1443
516
|
const root = {};
|
|
@@ -1481,7 +554,7 @@ function buildArticleTree(relPaths) {
|
|
|
1481
554
|
async function collectRelPaths(dir, rel = "") {
|
|
1482
555
|
let dirents;
|
|
1483
556
|
try {
|
|
1484
|
-
dirents = await
|
|
557
|
+
dirents = await import_promises8.default.readdir(dir, { withFileTypes: true });
|
|
1485
558
|
} catch {
|
|
1486
559
|
return [];
|
|
1487
560
|
}
|
|
@@ -1490,7 +563,7 @@ async function collectRelPaths(dir, rel = "") {
|
|
|
1490
563
|
if (d.name.startsWith(".")) continue;
|
|
1491
564
|
const childRel = rel ? `${rel}/${d.name}` : d.name;
|
|
1492
565
|
if (d.isDirectory()) {
|
|
1493
|
-
out.push(...await collectRelPaths(
|
|
566
|
+
out.push(...await collectRelPaths(import_node_path5.default.join(dir, d.name), childRel));
|
|
1494
567
|
} else if (/\.(mdx?)$/.test(d.name)) {
|
|
1495
568
|
out.push(childRel);
|
|
1496
569
|
}
|
|
@@ -1499,7 +572,7 @@ async function collectRelPaths(dir, rel = "") {
|
|
|
1499
572
|
}
|
|
1500
573
|
async function readdirSafe(dir) {
|
|
1501
574
|
try {
|
|
1502
|
-
return await
|
|
575
|
+
return await import_promises8.default.readdir(dir);
|
|
1503
576
|
} catch {
|
|
1504
577
|
return [];
|
|
1505
578
|
}
|
|
@@ -1516,7 +589,7 @@ function pickIndexFile(names) {
|
|
|
1516
589
|
async function cheapTitle(absPath) {
|
|
1517
590
|
let handle;
|
|
1518
591
|
try {
|
|
1519
|
-
handle = await
|
|
592
|
+
handle = await import_promises8.default.open(absPath, "r");
|
|
1520
593
|
const buf = Buffer.alloc(512);
|
|
1521
594
|
const { bytesRead } = await handle.read(buf, 0, 512, 0);
|
|
1522
595
|
const snippet = buf.subarray(0, bytesRead).toString("utf8");
|
|
@@ -1535,14 +608,14 @@ async function enrichTitles(nodes, collDir) {
|
|
|
1535
608
|
const children = await enrichTitles(node.children, collDir);
|
|
1536
609
|
let title = node.title;
|
|
1537
610
|
if (node.slug) {
|
|
1538
|
-
const dir =
|
|
611
|
+
const dir = import_node_path5.default.join(collDir, node.slug);
|
|
1539
612
|
const idx = pickIndexFile(await readdirSafe(dir));
|
|
1540
|
-
if (idx) title = await cheapTitle(
|
|
613
|
+
if (idx) title = await cheapTitle(import_node_path5.default.join(dir, idx)) ?? title;
|
|
1541
614
|
}
|
|
1542
615
|
return { ...node, title, children };
|
|
1543
616
|
}
|
|
1544
617
|
for (const ext of [".mdx", ".md"]) {
|
|
1545
|
-
const title = await cheapTitle(
|
|
618
|
+
const title = await cheapTitle(import_node_path5.default.join(collDir, `${node.slug}${ext}`));
|
|
1546
619
|
if (title) return { ...node, title };
|
|
1547
620
|
}
|
|
1548
621
|
return node;
|
|
@@ -1553,38 +626,38 @@ async function listArticleTree(coll) {
|
|
|
1553
626
|
const entry = await findCollection(coll);
|
|
1554
627
|
if (!entry) throw new CollectionNotFoundError(coll);
|
|
1555
628
|
const rels = await collectRelPaths(entry.path);
|
|
1556
|
-
return enrichTitles(buildArticleTree(rels),
|
|
629
|
+
return enrichTitles(buildArticleTree(rels), import_node_path5.default.resolve(entry.path));
|
|
1557
630
|
}
|
|
1558
631
|
async function readArticle(coll, slug) {
|
|
1559
632
|
const entry = await findCollection(coll);
|
|
1560
633
|
if (!entry) throw new CollectionNotFoundError(coll);
|
|
1561
|
-
const collDir =
|
|
634
|
+
const collDir = import_node_path5.default.resolve(entry.path);
|
|
1562
635
|
const normalized = slug.replace(/\\/g, "/");
|
|
1563
636
|
if (normalized.split("/").some((s) => s === "..")) {
|
|
1564
637
|
throw new InvalidTargetError(slug, "path traversal detected");
|
|
1565
638
|
}
|
|
1566
639
|
const safeSlug = normalized.split("/").filter((s) => s !== "" && s !== ".").join("/");
|
|
1567
|
-
const inJail = (p) => p === collDir || p.startsWith(collDir +
|
|
640
|
+
const inJail = (p) => p === collDir || p.startsWith(collDir + import_node_path5.default.sep);
|
|
1568
641
|
let abs;
|
|
1569
642
|
for (const rel of safeSlug ? [`${safeSlug}.mdx`, `${safeSlug}.md`] : []) {
|
|
1570
|
-
const candidate =
|
|
643
|
+
const candidate = import_node_path5.default.resolve(collDir, rel);
|
|
1571
644
|
if (!inJail(candidate)) throw new InvalidTargetError(slug, "path traversal detected");
|
|
1572
645
|
try {
|
|
1573
|
-
await
|
|
646
|
+
await import_promises8.default.access(candidate);
|
|
1574
647
|
abs = candidate;
|
|
1575
648
|
break;
|
|
1576
649
|
} catch {
|
|
1577
650
|
}
|
|
1578
651
|
}
|
|
1579
652
|
if (!abs) {
|
|
1580
|
-
const dir =
|
|
653
|
+
const dir = import_node_path5.default.resolve(collDir, safeSlug);
|
|
1581
654
|
if (inJail(dir)) {
|
|
1582
655
|
const idx = pickIndexFile(await readdirSafe(dir));
|
|
1583
|
-
if (idx) abs =
|
|
656
|
+
if (idx) abs = import_node_path5.default.join(dir, idx);
|
|
1584
657
|
}
|
|
1585
658
|
}
|
|
1586
659
|
if (!abs) throw new ArticleNotFoundError(`${coll}/${safeSlug}`);
|
|
1587
|
-
const parsed = parseArticle(await
|
|
660
|
+
const parsed = parseArticle(await import_promises8.default.readFile(abs, "utf8"));
|
|
1588
661
|
const title = parsed.title?.trim() || (safeSlug ? safeSlug.split("/").pop() ?? safeSlug : coll);
|
|
1589
662
|
return { coll, slug: safeSlug, title, tags: parsed.tags, body: parsed.body, path: abs };
|
|
1590
663
|
}
|
|
@@ -1652,34 +725,6 @@ async function renderArticle(body) {
|
|
|
1652
725
|
}).use(import_rehype_stringify.default).process(body);
|
|
1653
726
|
return { html: String(file), toc };
|
|
1654
727
|
}
|
|
1655
|
-
|
|
1656
|
-
// src/plugins/index.ts
|
|
1657
|
-
init_host();
|
|
1658
|
-
init_events();
|
|
1659
|
-
|
|
1660
|
-
// src/plugins/catalog.ts
|
|
1661
|
-
var KNOWN_PLUGINS = [
|
|
1662
|
-
{
|
|
1663
|
-
name: "diffwiki-qmd",
|
|
1664
|
-
kind: "search",
|
|
1665
|
-
spec: "diffwiki-qmd",
|
|
1666
|
-
description: "Local hybrid search \u2014 keyword, semantic, and reranked hybrid via the qmd CLI",
|
|
1667
|
-
requires: "qmd (npm i -g @tobilu/qmd)"
|
|
1668
|
-
},
|
|
1669
|
-
{
|
|
1670
|
-
name: "diffwiki-ripgrep",
|
|
1671
|
-
kind: "search",
|
|
1672
|
-
spec: "diffwiki-ripgrep",
|
|
1673
|
-
description: "Instant literal/regex search via ripgrep \u2014 no index, no models",
|
|
1674
|
-
requires: "rg (ripgrep)"
|
|
1675
|
-
}
|
|
1676
|
-
];
|
|
1677
|
-
function listAvailablePlugins() {
|
|
1678
|
-
return [...KNOWN_PLUGINS];
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1681
|
-
// src/plugins/index.ts
|
|
1682
|
-
init_registry2();
|
|
1683
728
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1684
729
|
0 && (module.exports = {
|
|
1685
730
|
ArticleNotFoundError,
|
|
@@ -1687,9 +732,7 @@ init_registry2();
|
|
|
1687
732
|
CollectionNotFoundError,
|
|
1688
733
|
DiffwikiError,
|
|
1689
734
|
InvalidTargetError,
|
|
1690
|
-
NATIVE_ENGINE,
|
|
1691
735
|
NoDefaultCollectionError,
|
|
1692
|
-
PluginError,
|
|
1693
736
|
REPO_CONFIG_FILE,
|
|
1694
737
|
addTags,
|
|
1695
738
|
buildArticleTree,
|
|
@@ -1698,56 +741,33 @@ init_registry2();
|
|
|
1698
741
|
configPath,
|
|
1699
742
|
createArticle,
|
|
1700
743
|
createCollection,
|
|
1701
|
-
deregisterPlugin,
|
|
1702
744
|
doctor,
|
|
1703
|
-
emitPluginEvent,
|
|
1704
|
-
ensurePluginRoot,
|
|
1705
745
|
findCollection,
|
|
1706
|
-
findEnabledPlugin,
|
|
1707
|
-
findPluginRecord,
|
|
1708
746
|
getConfigValue,
|
|
1709
|
-
getLogger,
|
|
1710
747
|
initRepoWiki,
|
|
1711
|
-
installPlugin,
|
|
1712
748
|
listArticleTree,
|
|
1713
|
-
listAvailablePlugins,
|
|
1714
749
|
listCollections,
|
|
1715
|
-
listEnabledPlugins,
|
|
1716
|
-
listPlugins,
|
|
1717
|
-
listSearchModes,
|
|
1718
|
-
loadSearchProvider,
|
|
1719
|
-
nativeSearch,
|
|
1720
750
|
parseAddTarget,
|
|
1721
751
|
parseArticle,
|
|
1722
752
|
parsePathTarget,
|
|
1723
|
-
pluginsDir,
|
|
1724
|
-
pluginsRegistryPath,
|
|
1725
753
|
query,
|
|
1726
754
|
readArticle,
|
|
1727
755
|
readConfig,
|
|
1728
|
-
readPluginRegistry,
|
|
1729
756
|
readRegistry,
|
|
1730
757
|
readRepoConfig,
|
|
1731
758
|
registerCollection,
|
|
1732
|
-
registerPlugin,
|
|
1733
759
|
registryPath,
|
|
1734
760
|
removeArticle,
|
|
1735
761
|
removeCollection,
|
|
1736
|
-
removePlugin,
|
|
1737
762
|
removeTags,
|
|
1738
763
|
renderArticle,
|
|
1739
|
-
resolveDefaultSearchMode,
|
|
1740
764
|
resolveHome,
|
|
1741
|
-
resolveLogLevel,
|
|
1742
|
-
search,
|
|
1743
765
|
serializeArticle,
|
|
1744
766
|
setConfigValue,
|
|
1745
767
|
setTags,
|
|
1746
768
|
slugify,
|
|
1747
769
|
unregisterCollection,
|
|
1748
770
|
updateArticleBody,
|
|
1749
|
-
upsertPlugin,
|
|
1750
771
|
writeConfig,
|
|
1751
|
-
writePluginRegistry,
|
|
1752
772
|
writeRegistry
|
|
1753
773
|
});
|