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