diffwiki-core 0.8.0 → 0.9.0-rc.202607291540.2b773e4
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 +232 -60
- package/dist/index.d.cts +118 -1
- package/dist/index.d.ts +118 -1
- package/dist/index.js +219 -58
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -570,6 +570,7 @@ __export(index_exports, {
|
|
|
570
570
|
InvalidTargetError: () => InvalidTargetError,
|
|
571
571
|
NATIVE_ENGINE: () => NATIVE_ENGINE,
|
|
572
572
|
NoDefaultCollectionError: () => NoDefaultCollectionError,
|
|
573
|
+
PROJECT_CONFIG_FILE: () => PROJECT_CONFIG_FILE,
|
|
573
574
|
PluginError: () => PluginError,
|
|
574
575
|
REPO_CONFIG_FILE: () => REPO_CONFIG_FILE,
|
|
575
576
|
WorktreeNotAllowedError: () => WorktreeNotAllowedError,
|
|
@@ -580,6 +581,7 @@ __export(index_exports, {
|
|
|
580
581
|
buildLinkGraph: () => buildLinkGraph,
|
|
581
582
|
buildNavTree: () => buildNavTree,
|
|
582
583
|
buildSearchIndex: () => buildSearchIndex,
|
|
584
|
+
buildSiteConfig: () => buildSiteConfig,
|
|
583
585
|
collectionDir: () => collectionDir,
|
|
584
586
|
collectionGitStatus: () => collectionGitStatus,
|
|
585
587
|
collectionMeta: () => collectionMeta,
|
|
@@ -624,8 +626,10 @@ __export(index_exports, {
|
|
|
624
626
|
readArticle: () => readArticle,
|
|
625
627
|
readConfig: () => readConfig,
|
|
626
628
|
readPluginRegistry: () => readPluginRegistry,
|
|
629
|
+
readProjectConfig: () => readProjectConfig,
|
|
627
630
|
readRegistry: () => readRegistry,
|
|
628
631
|
readRepoConfig: () => readRepoConfig,
|
|
632
|
+
readSiteConfig: () => readSiteConfig,
|
|
629
633
|
registerCollection: () => registerCollection,
|
|
630
634
|
registerPlugin: () => registerPlugin,
|
|
631
635
|
registryPath: () => registryPath,
|
|
@@ -639,10 +643,14 @@ __export(index_exports, {
|
|
|
639
643
|
resolveCollectionSelection: () => resolveCollectionSelection,
|
|
640
644
|
resolveCwdCollection: () => resolveCwdCollection,
|
|
641
645
|
resolveDefaultSearchMode: () => resolveDefaultSearchMode,
|
|
646
|
+
resolveEditTarget: () => resolveEditTarget,
|
|
647
|
+
resolveEditorCommand: () => resolveEditorCommand,
|
|
642
648
|
resolveHome: () => resolveHome,
|
|
643
649
|
resolveLogLevel: () => resolveLogLevel,
|
|
644
650
|
resolvePage: () => resolvePage,
|
|
651
|
+
resolveProjectCollections: () => resolveProjectCollections,
|
|
645
652
|
search: () => search,
|
|
653
|
+
seedProjectHome: () => seedProjectHome,
|
|
646
654
|
serializeArticle: () => serializeArticle,
|
|
647
655
|
setConfigValue: () => setConfigValue,
|
|
648
656
|
setTags: () => setTags,
|
|
@@ -1127,12 +1135,120 @@ async function collectionGitStatus(entry) {
|
|
|
1127
1135
|
return { branch: s.branch, ahead: s.ahead, behind: s.behind, hasUpstream: s.hasUpstream };
|
|
1128
1136
|
}
|
|
1129
1137
|
|
|
1138
|
+
// src/project.ts
|
|
1139
|
+
var import_promises8 = __toESM(require("fs/promises"), 1);
|
|
1140
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
1141
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
1142
|
+
var import_yaml3 = require("yaml");
|
|
1143
|
+
var PROJECT_CONFIG_FILE = "diffwiki.yaml";
|
|
1144
|
+
function normaliseSite(raw) {
|
|
1145
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
1146
|
+
const r = raw;
|
|
1147
|
+
const repo = typeof r.repo === "string" ? r.repo : void 0;
|
|
1148
|
+
let copyright;
|
|
1149
|
+
if (typeof r.copyright === "string") {
|
|
1150
|
+
copyright = { text: r.copyright };
|
|
1151
|
+
} else if (r.copyright && typeof r.copyright === "object") {
|
|
1152
|
+
const c = r.copyright;
|
|
1153
|
+
if (typeof c.text === "string") {
|
|
1154
|
+
copyright = { text: c.text, ...typeof c.url === "string" ? { url: c.url } : {} };
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
let home;
|
|
1158
|
+
if (r.home && typeof r.home === "object") {
|
|
1159
|
+
const h = r.home;
|
|
1160
|
+
const tagline = typeof h.tagline === "string" ? h.tagline : void 0;
|
|
1161
|
+
const cards = [];
|
|
1162
|
+
if (Array.isArray(h.cards)) {
|
|
1163
|
+
for (const c of h.cards) {
|
|
1164
|
+
if (!c || typeof c !== "object") continue;
|
|
1165
|
+
const cc = c;
|
|
1166
|
+
if (typeof cc.title === "string" && typeof cc.link === "string") {
|
|
1167
|
+
cards.push({
|
|
1168
|
+
title: cc.title,
|
|
1169
|
+
link: cc.link,
|
|
1170
|
+
...typeof cc.icon === "string" ? { icon: cc.icon } : {},
|
|
1171
|
+
...typeof cc.subtitle === "string" ? { subtitle: cc.subtitle } : {}
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
home = { ...tagline !== void 0 ? { tagline } : {}, ...cards.length > 0 ? { cards } : {} };
|
|
1177
|
+
if (Object.keys(home).length === 0) home = void 0;
|
|
1178
|
+
}
|
|
1179
|
+
if (repo === void 0 && copyright === void 0 && home === void 0) return void 0;
|
|
1180
|
+
return {
|
|
1181
|
+
...repo !== void 0 ? { repo } : {},
|
|
1182
|
+
...copyright !== void 0 ? { copyright } : {},
|
|
1183
|
+
...home !== void 0 ? { home } : {}
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
async function readProjectConfig(dir) {
|
|
1187
|
+
let raw;
|
|
1188
|
+
try {
|
|
1189
|
+
raw = await import_promises8.default.readFile(import_node_path6.default.join(dir, PROJECT_CONFIG_FILE), "utf8");
|
|
1190
|
+
} catch (err) {
|
|
1191
|
+
if (err.code === "ENOENT") return void 0;
|
|
1192
|
+
throw err;
|
|
1193
|
+
}
|
|
1194
|
+
const parsed = (0, import_yaml3.parse)(raw) ?? {};
|
|
1195
|
+
const collections = [];
|
|
1196
|
+
if (Array.isArray(parsed.collections)) {
|
|
1197
|
+
for (const c of parsed.collections) {
|
|
1198
|
+
if (!c || typeof c !== "object") continue;
|
|
1199
|
+
const cc = c;
|
|
1200
|
+
if (typeof cc.name === "string" && typeof cc.path === "string") {
|
|
1201
|
+
collections.push({
|
|
1202
|
+
name: cc.name,
|
|
1203
|
+
path: cc.path,
|
|
1204
|
+
...typeof cc.order === "number" ? { order: cc.order } : {}
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
collections.sort((a, b) => (a.order ?? Number.MAX_SAFE_INTEGER) - (b.order ?? Number.MAX_SAFE_INTEGER));
|
|
1209
|
+
} else if (typeof parsed.path === "string") {
|
|
1210
|
+
const name = typeof parsed.collection === "string" ? parsed.collection : import_node_path6.default.basename(import_node_path6.default.resolve(dir));
|
|
1211
|
+
collections.push({ name, path: parsed.path });
|
|
1212
|
+
}
|
|
1213
|
+
const site = normaliseSite(parsed.site);
|
|
1214
|
+
return { ...site ? { site } : {}, collections };
|
|
1215
|
+
}
|
|
1216
|
+
async function resolveProjectCollections(dir) {
|
|
1217
|
+
const config = await readProjectConfig(dir);
|
|
1218
|
+
if (!config) return [];
|
|
1219
|
+
const root = import_node_path6.default.resolve(dir);
|
|
1220
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1221
|
+
return config.collections.map((c) => ({
|
|
1222
|
+
name: c.name,
|
|
1223
|
+
type: "repo",
|
|
1224
|
+
path: import_node_path6.default.resolve(root, c.path),
|
|
1225
|
+
createdAt
|
|
1226
|
+
}));
|
|
1227
|
+
}
|
|
1228
|
+
async function readSiteConfig(dir) {
|
|
1229
|
+
return (await readProjectConfig(dir))?.site ?? {};
|
|
1230
|
+
}
|
|
1231
|
+
async function seedProjectHome(dir) {
|
|
1232
|
+
const entries = await resolveProjectCollections(dir);
|
|
1233
|
+
if (entries.length === 0) {
|
|
1234
|
+
throw new Error(`No collections to render from diffwiki.yaml in ${import_node_path6.default.resolve(dir)}`);
|
|
1235
|
+
}
|
|
1236
|
+
const home = await import_promises8.default.mkdtemp(import_node_path6.default.join(import_node_os2.default.tmpdir(), "diffwiki-project-"));
|
|
1237
|
+
await import_promises8.default.writeFile(
|
|
1238
|
+
import_node_path6.default.join(home, "registry.json"),
|
|
1239
|
+
`${JSON.stringify({ version: 1, collections: entries }, null, 2)}
|
|
1240
|
+
`,
|
|
1241
|
+
"utf8"
|
|
1242
|
+
);
|
|
1243
|
+
return { home, entries };
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1130
1246
|
// src/site.ts
|
|
1131
1247
|
init_collections();
|
|
1132
1248
|
|
|
1133
1249
|
// src/browse.ts
|
|
1134
|
-
var
|
|
1135
|
-
var
|
|
1250
|
+
var import_promises9 = __toESM(require("fs/promises"), 1);
|
|
1251
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
1136
1252
|
init_collections();
|
|
1137
1253
|
init_errors();
|
|
1138
1254
|
var INDEX_RE = /^(index|readme)\.mdx?$/i;
|
|
@@ -1178,7 +1294,7 @@ function buildArticleTree(relPaths) {
|
|
|
1178
1294
|
async function collectRelPaths(dir, rel = "") {
|
|
1179
1295
|
let dirents;
|
|
1180
1296
|
try {
|
|
1181
|
-
dirents = await
|
|
1297
|
+
dirents = await import_promises9.default.readdir(dir, { withFileTypes: true });
|
|
1182
1298
|
} catch {
|
|
1183
1299
|
return [];
|
|
1184
1300
|
}
|
|
@@ -1187,7 +1303,7 @@ async function collectRelPaths(dir, rel = "") {
|
|
|
1187
1303
|
if (d.name.startsWith(".")) continue;
|
|
1188
1304
|
const childRel = rel ? `${rel}/${d.name}` : d.name;
|
|
1189
1305
|
if (d.isDirectory()) {
|
|
1190
|
-
out.push(...await collectRelPaths(
|
|
1306
|
+
out.push(...await collectRelPaths(import_node_path7.default.join(dir, d.name), childRel));
|
|
1191
1307
|
} else if (/\.(mdx?)$/.test(d.name)) {
|
|
1192
1308
|
out.push(childRel);
|
|
1193
1309
|
}
|
|
@@ -1196,7 +1312,7 @@ async function collectRelPaths(dir, rel = "") {
|
|
|
1196
1312
|
}
|
|
1197
1313
|
async function readdirSafe(dir) {
|
|
1198
1314
|
try {
|
|
1199
|
-
return await
|
|
1315
|
+
return await import_promises9.default.readdir(dir);
|
|
1200
1316
|
} catch {
|
|
1201
1317
|
return [];
|
|
1202
1318
|
}
|
|
@@ -1214,7 +1330,7 @@ var CHEAP_META_BYTES = 8192;
|
|
|
1214
1330
|
async function cheapMeta(absPath) {
|
|
1215
1331
|
let handle;
|
|
1216
1332
|
try {
|
|
1217
|
-
handle = await
|
|
1333
|
+
handle = await import_promises9.default.open(absPath, "r");
|
|
1218
1334
|
const buf = Buffer.alloc(CHEAP_META_BYTES);
|
|
1219
1335
|
const { bytesRead } = await handle.read(buf, 0, CHEAP_META_BYTES, 0);
|
|
1220
1336
|
const snippet = buf.subarray(0, bytesRead).toString("utf8");
|
|
@@ -1225,10 +1341,13 @@ async function cheapMeta(absPath) {
|
|
|
1225
1341
|
const titleM = /^title:\s*(.+)$/m.exec(inner);
|
|
1226
1342
|
const audienceM = /^audience:\s*(.+)$/m.exec(inner);
|
|
1227
1343
|
const statusM = /^status:\s*(.+)$/m.exec(inner);
|
|
1344
|
+
const orderM = /^order:\s*(.+)$/m.exec(inner);
|
|
1228
1345
|
const title = titleM ? clean(titleM[1]) : void 0;
|
|
1229
1346
|
const audience = audienceM && clean(audienceM[1]) === "private" ? "private" : void 0;
|
|
1230
1347
|
const status = statusM && clean(statusM[1]) === "draft" ? "draft" : void 0;
|
|
1231
|
-
|
|
1348
|
+
const orderNum = orderM ? Number(clean(orderM[1])) : NaN;
|
|
1349
|
+
const order = Number.isFinite(orderNum) ? orderNum : void 0;
|
|
1350
|
+
return { title, audience, status, order };
|
|
1232
1351
|
} catch {
|
|
1233
1352
|
} finally {
|
|
1234
1353
|
await handle?.close();
|
|
@@ -1236,86 +1355,90 @@ async function cheapMeta(absPath) {
|
|
|
1236
1355
|
return {};
|
|
1237
1356
|
}
|
|
1238
1357
|
async function enrichTitles(nodes, collDir) {
|
|
1239
|
-
|
|
1358
|
+
const enriched = await Promise.all(
|
|
1240
1359
|
nodes.map(async (node) => {
|
|
1241
1360
|
if (node.children) {
|
|
1242
1361
|
const children = await enrichTitles(node.children, collDir);
|
|
1243
1362
|
let title = node.title;
|
|
1244
1363
|
let audience = node.audience;
|
|
1245
1364
|
let status = node.status;
|
|
1365
|
+
let order = node.order;
|
|
1246
1366
|
if (node.slug) {
|
|
1247
|
-
const dir =
|
|
1367
|
+
const dir = import_node_path7.default.join(collDir, node.slug);
|
|
1248
1368
|
const idx = pickIndexFile(await readdirSafe(dir));
|
|
1249
1369
|
if (idx) {
|
|
1250
|
-
const meta = await cheapMeta(
|
|
1370
|
+
const meta = await cheapMeta(import_node_path7.default.join(dir, idx));
|
|
1251
1371
|
title = meta.title ?? title;
|
|
1252
1372
|
audience = meta.audience ?? audience;
|
|
1253
1373
|
status = meta.status ?? status;
|
|
1374
|
+
order = meta.order ?? order;
|
|
1254
1375
|
}
|
|
1255
1376
|
}
|
|
1256
|
-
return { ...node, title, audience, status, children };
|
|
1377
|
+
return { ...node, title, audience, status, order, children };
|
|
1257
1378
|
}
|
|
1258
1379
|
for (const ext of [".mdx", ".md"]) {
|
|
1259
|
-
const meta = await cheapMeta(
|
|
1260
|
-
if (meta.title || meta.audience || meta.status) {
|
|
1380
|
+
const meta = await cheapMeta(import_node_path7.default.join(collDir, `${node.slug}${ext}`));
|
|
1381
|
+
if (meta.title || meta.audience || meta.status || meta.order != null) {
|
|
1261
1382
|
return {
|
|
1262
1383
|
...node,
|
|
1263
1384
|
title: meta.title ?? node.title,
|
|
1264
1385
|
audience: meta.audience ?? node.audience,
|
|
1265
|
-
status: meta.status ?? node.status
|
|
1386
|
+
status: meta.status ?? node.status,
|
|
1387
|
+
order: meta.order ?? node.order
|
|
1266
1388
|
};
|
|
1267
1389
|
}
|
|
1268
1390
|
}
|
|
1269
1391
|
return node;
|
|
1270
1392
|
})
|
|
1271
1393
|
);
|
|
1394
|
+
return enriched.sort((a, b) => (a.order ?? Number.MAX_SAFE_INTEGER) - (b.order ?? Number.MAX_SAFE_INTEGER));
|
|
1272
1395
|
}
|
|
1273
1396
|
async function collectionMeta(collDir) {
|
|
1274
1397
|
const idx = pickIndexFile(await readdirSafe(collDir));
|
|
1275
1398
|
if (!idx) return {};
|
|
1276
|
-
const meta = await cheapMeta(
|
|
1277
|
-
return { audience: meta.audience, status: meta.status };
|
|
1399
|
+
const meta = await cheapMeta(import_node_path7.default.join(collDir, idx));
|
|
1400
|
+
return { title: meta.title, audience: meta.audience, status: meta.status };
|
|
1278
1401
|
}
|
|
1279
1402
|
async function listArticleTree(coll) {
|
|
1280
1403
|
const entry = await findCollection(coll);
|
|
1281
1404
|
if (!entry) throw new CollectionNotFoundError(coll);
|
|
1282
1405
|
const rels = await collectRelPaths(entry.path);
|
|
1283
|
-
return enrichTitles(buildArticleTree(rels),
|
|
1406
|
+
return enrichTitles(buildArticleTree(rels), import_node_path7.default.resolve(entry.path));
|
|
1284
1407
|
}
|
|
1285
1408
|
async function readArticle(coll, slug) {
|
|
1286
1409
|
const entry = await findCollection(coll);
|
|
1287
1410
|
if (!entry) throw new CollectionNotFoundError(coll);
|
|
1288
|
-
const collDir =
|
|
1411
|
+
const collDir = import_node_path7.default.resolve(entry.path);
|
|
1289
1412
|
const normalized = slug.replace(/\\/g, "/");
|
|
1290
1413
|
if (normalized.split("/").some((s) => s === "..")) {
|
|
1291
1414
|
throw new InvalidTargetError(slug, "path traversal detected");
|
|
1292
1415
|
}
|
|
1293
1416
|
const safeSlug = normalized.split("/").filter((s) => s !== "" && s !== ".").join("/");
|
|
1294
|
-
const inJail = (p) => p === collDir || p.startsWith(collDir +
|
|
1417
|
+
const inJail = (p) => p === collDir || p.startsWith(collDir + import_node_path7.default.sep);
|
|
1295
1418
|
let abs;
|
|
1296
1419
|
let isIndex = false;
|
|
1297
1420
|
for (const rel of safeSlug ? [`${safeSlug}.mdx`, `${safeSlug}.md`] : []) {
|
|
1298
|
-
const candidate =
|
|
1421
|
+
const candidate = import_node_path7.default.resolve(collDir, rel);
|
|
1299
1422
|
if (!inJail(candidate)) throw new InvalidTargetError(slug, "path traversal detected");
|
|
1300
1423
|
try {
|
|
1301
|
-
await
|
|
1424
|
+
await import_promises9.default.access(candidate);
|
|
1302
1425
|
abs = candidate;
|
|
1303
1426
|
break;
|
|
1304
1427
|
} catch {
|
|
1305
1428
|
}
|
|
1306
1429
|
}
|
|
1307
1430
|
if (!abs) {
|
|
1308
|
-
const dir =
|
|
1431
|
+
const dir = import_node_path7.default.resolve(collDir, safeSlug);
|
|
1309
1432
|
if (inJail(dir)) {
|
|
1310
1433
|
const idx = pickIndexFile(await readdirSafe(dir));
|
|
1311
1434
|
if (idx) {
|
|
1312
|
-
abs =
|
|
1435
|
+
abs = import_node_path7.default.join(dir, idx);
|
|
1313
1436
|
isIndex = true;
|
|
1314
1437
|
}
|
|
1315
1438
|
}
|
|
1316
1439
|
}
|
|
1317
1440
|
if (!abs) throw new ArticleNotFoundError(`${coll}/${safeSlug}`);
|
|
1318
|
-
const parsed = parseArticle(await
|
|
1441
|
+
const parsed = parseArticle(await import_promises9.default.readFile(abs, "utf8"));
|
|
1319
1442
|
const title = parsed.title?.trim() || (safeSlug ? safeSlug.split("/").pop() ?? safeSlug : coll);
|
|
1320
1443
|
return {
|
|
1321
1444
|
coll,
|
|
@@ -1331,7 +1454,7 @@ async function readArticle(coll, slug) {
|
|
|
1331
1454
|
}
|
|
1332
1455
|
|
|
1333
1456
|
// src/render.ts
|
|
1334
|
-
var
|
|
1457
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
1335
1458
|
var import_unified = require("unified");
|
|
1336
1459
|
var import_remark_parse = __toESM(require("remark-parse"), 1);
|
|
1337
1460
|
var import_remark_gfm = __toESM(require("remark-gfm"), 1);
|
|
@@ -1348,7 +1471,7 @@ function isNonRelativeHref(href) {
|
|
|
1348
1471
|
return /^[a-z][a-z0-9+.-]*:/i.test(href);
|
|
1349
1472
|
}
|
|
1350
1473
|
function rehypeRewriteLinks(ctx) {
|
|
1351
|
-
const slugDir = ctx.isIndex ? ctx.slug || "." :
|
|
1474
|
+
const slugDir = ctx.isIndex ? ctx.slug || "." : import_node_path8.default.posix.dirname(ctx.slug || ".");
|
|
1352
1475
|
return (tree) => {
|
|
1353
1476
|
(0, import_unist_util_visit.visit)(tree, "element", (node) => {
|
|
1354
1477
|
if (node.tagName !== "a") return;
|
|
@@ -1358,8 +1481,8 @@ function rehypeRewriteLinks(ctx) {
|
|
|
1358
1481
|
const suffix = suffixMatch ? suffixMatch[0] : "";
|
|
1359
1482
|
const pathPart = suffix ? raw.slice(0, raw.length - suffix.length) : raw;
|
|
1360
1483
|
if (pathPart === "") return;
|
|
1361
|
-
const joined =
|
|
1362
|
-
const normalized =
|
|
1484
|
+
const joined = import_node_path8.default.posix.join(slugDir === "." ? "" : slugDir, pathPart);
|
|
1485
|
+
const normalized = import_node_path8.default.posix.normalize(joined);
|
|
1363
1486
|
const toRoute = (p) => p.replace(/\.(mdx?)$/i, "").replace(/(^|\/)(index|readme)$/i, "$1").replace(/\/+$/, "").replace(/^\/+/, "");
|
|
1364
1487
|
if (normalized.startsWith("../")) {
|
|
1365
1488
|
const sibling = normalized.slice(3);
|
|
@@ -1477,6 +1600,10 @@ async function renderArticle(body, ctx) {
|
|
|
1477
1600
|
themes: { light: "github-light", dark: "github-dark" },
|
|
1478
1601
|
defaultColor: false,
|
|
1479
1602
|
langAlias: LANG_ALIASES,
|
|
1603
|
+
// `defaultLanguage` styles fences with no language, `fallbackLanguage` those
|
|
1604
|
+
// with an unknown one — both as plaintext, so every code block renders as a
|
|
1605
|
+
// themed `pre.shiki` (light background + copy button), never a bare dark <pre>.
|
|
1606
|
+
defaultLanguage: "plaintext",
|
|
1480
1607
|
fallbackLanguage: "plaintext"
|
|
1481
1608
|
}).use(import_rehype_stringify.default).process(body);
|
|
1482
1609
|
return { html: String(file), toc, links };
|
|
@@ -1613,11 +1740,12 @@ async function buildCollections() {
|
|
|
1613
1740
|
const entries = await listCollections();
|
|
1614
1741
|
const infos = await Promise.all(
|
|
1615
1742
|
entries.map(async (e) => {
|
|
1616
|
-
const { audience, status } = await collectionMeta(e.path);
|
|
1743
|
+
const { title, audience, status } = await collectionMeta(e.path);
|
|
1617
1744
|
return {
|
|
1618
1745
|
name: e.name,
|
|
1619
1746
|
type: e.type,
|
|
1620
1747
|
path: e.path,
|
|
1748
|
+
...title ? { title } : {},
|
|
1621
1749
|
...await gitInfo(e),
|
|
1622
1750
|
...audience ? { audience } : {},
|
|
1623
1751
|
...status ? { status } : {}
|
|
@@ -1630,12 +1758,13 @@ async function buildNavTree() {
|
|
|
1630
1758
|
const entries = await listCollections();
|
|
1631
1759
|
const trees = await Promise.all(
|
|
1632
1760
|
entries.map(async (e) => {
|
|
1633
|
-
const { audience, status } = await collectionMeta(e.path);
|
|
1761
|
+
const { title, audience, status } = await collectionMeta(e.path);
|
|
1634
1762
|
return {
|
|
1635
1763
|
collection: {
|
|
1636
1764
|
name: e.name,
|
|
1637
1765
|
type: e.type,
|
|
1638
1766
|
path: e.path,
|
|
1767
|
+
...title ? { title } : {},
|
|
1639
1768
|
...await gitInfo(e),
|
|
1640
1769
|
...audience ? { audience } : {},
|
|
1641
1770
|
...status ? { status } : {}
|
|
@@ -1708,6 +1837,15 @@ async function resolvePage(coll, slug, opts) {
|
|
|
1708
1837
|
};
|
|
1709
1838
|
}
|
|
1710
1839
|
}
|
|
1840
|
+
async function buildSiteConfig() {
|
|
1841
|
+
const dir = process.env.DIFFWIKI_PROJECT_DIR;
|
|
1842
|
+
if (!dir) return {};
|
|
1843
|
+
try {
|
|
1844
|
+
return await readSiteConfig(dir);
|
|
1845
|
+
} catch {
|
|
1846
|
+
return {};
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1711
1849
|
|
|
1712
1850
|
// src/select.ts
|
|
1713
1851
|
function globToRegExp(glob) {
|
|
@@ -1737,8 +1875,8 @@ function resolveCollectionSelection(all, sel = {}) {
|
|
|
1737
1875
|
}
|
|
1738
1876
|
|
|
1739
1877
|
// src/query.ts
|
|
1740
|
-
var
|
|
1741
|
-
var
|
|
1878
|
+
var import_promises10 = __toESM(require("fs/promises"), 1);
|
|
1879
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
1742
1880
|
init_collections();
|
|
1743
1881
|
init_errors();
|
|
1744
1882
|
init_logger();
|
|
@@ -1807,13 +1945,13 @@ var log2 = getLogger("query");
|
|
|
1807
1945
|
async function collectMarkdownFiles(dir) {
|
|
1808
1946
|
let entries;
|
|
1809
1947
|
try {
|
|
1810
|
-
entries = await
|
|
1948
|
+
entries = await import_promises10.default.readdir(dir, { withFileTypes: true });
|
|
1811
1949
|
} catch {
|
|
1812
1950
|
return [];
|
|
1813
1951
|
}
|
|
1814
1952
|
const files = [];
|
|
1815
1953
|
for (const entry of entries) {
|
|
1816
|
-
const full =
|
|
1954
|
+
const full = import_node_path9.default.join(dir, entry.name);
|
|
1817
1955
|
if (entry.isDirectory()) {
|
|
1818
1956
|
files.push(...await collectMarkdownFiles(full));
|
|
1819
1957
|
} else if (entry.isFile() && /\.mdx?$/.test(entry.name)) {
|
|
@@ -1832,12 +1970,12 @@ async function nativeSearch(term, collections, collection) {
|
|
|
1832
1970
|
const docs = [];
|
|
1833
1971
|
for (const entry of targets) {
|
|
1834
1972
|
for (const full of await collectMarkdownFiles(entry.path)) {
|
|
1835
|
-
const fallbackTitle =
|
|
1973
|
+
const fallbackTitle = import_node_path9.default.basename(full).replace(/\.mdx?$/, "");
|
|
1836
1974
|
let title = fallbackTitle;
|
|
1837
1975
|
let tags = [];
|
|
1838
1976
|
let body = "";
|
|
1839
1977
|
try {
|
|
1840
|
-
const parsed = parseArticle(await
|
|
1978
|
+
const parsed = parseArticle(await import_promises10.default.readFile(full, "utf8"));
|
|
1841
1979
|
if (parsed.title) title = parsed.title;
|
|
1842
1980
|
tags = parsed.tags;
|
|
1843
1981
|
body = parsed.body;
|
|
@@ -1920,14 +2058,14 @@ async function query(term, opts) {
|
|
|
1920
2058
|
}
|
|
1921
2059
|
|
|
1922
2060
|
// src/search.ts
|
|
1923
|
-
var
|
|
1924
|
-
var
|
|
2061
|
+
var import_promises11 = __toESM(require("fs/promises"), 1);
|
|
2062
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
1925
2063
|
init_collections();
|
|
1926
2064
|
var isStatic2 = () => process.env.DIFFWIKI_STATIC === "1";
|
|
1927
2065
|
async function readArticleParts(collDir, slug) {
|
|
1928
2066
|
for (const ext of [".mdx", ".md"]) {
|
|
1929
2067
|
try {
|
|
1930
|
-
const parsed = parseArticle(await
|
|
2068
|
+
const parsed = parseArticle(await import_promises11.default.readFile(import_node_path10.default.join(collDir, `${slug}${ext}`), "utf8"));
|
|
1931
2069
|
return { tags: parsed.tags, body: parsed.body };
|
|
1932
2070
|
} catch {
|
|
1933
2071
|
}
|
|
@@ -1962,7 +2100,7 @@ async function buildSearchIndex(collection) {
|
|
|
1962
2100
|
for (const e of entries) {
|
|
1963
2101
|
try {
|
|
1964
2102
|
const collEntry = await findCollection(e.name);
|
|
1965
|
-
const collDir =
|
|
2103
|
+
const collDir = import_node_path10.default.resolve(collEntry?.path ?? e.path);
|
|
1966
2104
|
if (isStatic2()) {
|
|
1967
2105
|
const cm = await collectionMeta(collDir);
|
|
1968
2106
|
if (cm.audience === "private" || cm.status === "draft") continue;
|
|
@@ -1975,15 +2113,15 @@ async function buildSearchIndex(collection) {
|
|
|
1975
2113
|
}
|
|
1976
2114
|
|
|
1977
2115
|
// src/doctor.ts
|
|
1978
|
-
var
|
|
2116
|
+
var import_promises13 = __toESM(require("fs/promises"), 1);
|
|
1979
2117
|
init_paths();
|
|
1980
2118
|
init_registry();
|
|
1981
2119
|
init_collections();
|
|
1982
2120
|
|
|
1983
2121
|
// src/plugins/manager.ts
|
|
1984
|
-
var
|
|
1985
|
-
var
|
|
1986
|
-
var
|
|
2122
|
+
var import_promises12 = __toESM(require("fs/promises"), 1);
|
|
2123
|
+
var import_node_os3 = __toESM(require("os"), 1);
|
|
2124
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
1987
2125
|
init_paths();
|
|
1988
2126
|
init_errors();
|
|
1989
2127
|
init_collections();
|
|
@@ -1994,7 +2132,7 @@ init_host();
|
|
|
1994
2132
|
var import_node_child_process4 = require("child_process");
|
|
1995
2133
|
var import_node_util3 = require("util");
|
|
1996
2134
|
var import_node_fs = require("fs");
|
|
1997
|
-
var
|
|
2135
|
+
var import_node_path11 = require("path");
|
|
1998
2136
|
var import_node_url = require("url");
|
|
1999
2137
|
var execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process4.execFile);
|
|
2000
2138
|
async function runProcess(file, args, opts) {
|
|
@@ -2010,7 +2148,7 @@ async function runProcess(file, args, opts) {
|
|
|
2010
2148
|
// src/plugins/manager.ts
|
|
2011
2149
|
async function readPackageJson(file) {
|
|
2012
2150
|
try {
|
|
2013
|
-
return JSON.parse(await
|
|
2151
|
+
return JSON.parse(await import_promises12.default.readFile(file, "utf8"));
|
|
2014
2152
|
} catch {
|
|
2015
2153
|
return null;
|
|
2016
2154
|
}
|
|
@@ -2026,7 +2164,7 @@ function binOf(manifest) {
|
|
|
2026
2164
|
async function pluginManifestDirs(nodeModules) {
|
|
2027
2165
|
let entries;
|
|
2028
2166
|
try {
|
|
2029
|
-
entries = await
|
|
2167
|
+
entries = await import_promises12.default.readdir(nodeModules);
|
|
2030
2168
|
} catch {
|
|
2031
2169
|
return [];
|
|
2032
2170
|
}
|
|
@@ -2036,44 +2174,44 @@ async function pluginManifestDirs(nodeModules) {
|
|
|
2036
2174
|
if (entry.startsWith("@")) {
|
|
2037
2175
|
let scoped;
|
|
2038
2176
|
try {
|
|
2039
|
-
scoped = await
|
|
2177
|
+
scoped = await import_promises12.default.readdir(import_node_path12.default.join(nodeModules, entry));
|
|
2040
2178
|
} catch {
|
|
2041
2179
|
continue;
|
|
2042
2180
|
}
|
|
2043
2181
|
for (const pkg of scoped) {
|
|
2044
2182
|
if (pkg.startsWith(".")) continue;
|
|
2045
|
-
dirs.push(
|
|
2183
|
+
dirs.push(import_node_path12.default.join(nodeModules, entry, pkg));
|
|
2046
2184
|
}
|
|
2047
2185
|
} else {
|
|
2048
|
-
dirs.push(
|
|
2186
|
+
dirs.push(import_node_path12.default.join(nodeModules, entry));
|
|
2049
2187
|
}
|
|
2050
2188
|
}
|
|
2051
2189
|
return dirs;
|
|
2052
2190
|
}
|
|
2053
2191
|
async function ensurePluginRoot() {
|
|
2054
2192
|
const dir = pluginsDir();
|
|
2055
|
-
await
|
|
2056
|
-
const pkgPath =
|
|
2193
|
+
await import_promises12.default.mkdir(dir, { recursive: true });
|
|
2194
|
+
const pkgPath = import_node_path12.default.join(dir, "package.json");
|
|
2057
2195
|
if (await readPackageJson(pkgPath) === null) {
|
|
2058
2196
|
const pkg = { name: "diffwiki-plugins", private: true, version: "0.0.0" };
|
|
2059
|
-
await
|
|
2197
|
+
await import_promises12.default.writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
2060
2198
|
`, "utf8");
|
|
2061
2199
|
}
|
|
2062
2200
|
return dir;
|
|
2063
2201
|
}
|
|
2064
2202
|
async function asSearchPackage(pkgDir) {
|
|
2065
|
-
const manifest = await readPackageJson(
|
|
2203
|
+
const manifest = await readPackageJson(import_node_path12.default.join(pkgDir, "package.json"));
|
|
2066
2204
|
if (!manifest?.name || manifest.diffwiki?.kind !== "search") return null;
|
|
2067
2205
|
const bin = binOf(manifest);
|
|
2068
2206
|
if (!bin) return null;
|
|
2069
|
-
return { name: manifest.name, version: manifest.version, binAbs:
|
|
2207
|
+
return { name: manifest.name, version: manifest.version, binAbs: import_node_path12.default.resolve(pkgDir, bin) };
|
|
2070
2208
|
}
|
|
2071
2209
|
async function packageNameFromSpec(spec) {
|
|
2072
2210
|
const looksLikePath = spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("~") || spec.startsWith("file:");
|
|
2073
2211
|
if (looksLikePath) {
|
|
2074
2212
|
const raw = spec.startsWith("file:") ? spec.slice("file:".length) : spec;
|
|
2075
|
-
const abs =
|
|
2076
|
-
return (await readPackageJson(
|
|
2213
|
+
const abs = import_node_path12.default.resolve(raw.replace(/^~(?=$|\/)/, import_node_os3.default.homedir()));
|
|
2214
|
+
return (await readPackageJson(import_node_path12.default.join(abs, "package.json")))?.name ?? null;
|
|
2077
2215
|
}
|
|
2078
2216
|
if (/^(git\+|git:|https?:|ssh:)/.test(spec)) return null;
|
|
2079
2217
|
if (spec.startsWith("@")) {
|
|
@@ -2084,10 +2222,10 @@ async function packageNameFromSpec(spec) {
|
|
|
2084
2222
|
return at <= 0 ? spec : spec.slice(0, at);
|
|
2085
2223
|
}
|
|
2086
2224
|
async function resolveSearchPackage(dir, name) {
|
|
2087
|
-
return asSearchPackage(
|
|
2225
|
+
return asSearchPackage(import_node_path12.default.join(dir, "node_modules", ...name.split("/")));
|
|
2088
2226
|
}
|
|
2089
2227
|
async function discoverSearchPackage(dir) {
|
|
2090
|
-
const nodeModules =
|
|
2228
|
+
const nodeModules = import_node_path12.default.join(dir, "node_modules");
|
|
2091
2229
|
for (const pkgDir of await pluginManifestDirs(nodeModules)) {
|
|
2092
2230
|
const found = await asSearchPackage(pkgDir);
|
|
2093
2231
|
if (found) return found;
|
|
@@ -2219,7 +2357,7 @@ init_host();
|
|
|
2219
2357
|
init_types();
|
|
2220
2358
|
async function exists(target) {
|
|
2221
2359
|
try {
|
|
2222
|
-
await
|
|
2360
|
+
await import_promises13.default.access(target);
|
|
2223
2361
|
return true;
|
|
2224
2362
|
} catch {
|
|
2225
2363
|
return false;
|
|
@@ -2327,6 +2465,32 @@ async function appendPluginDiagnostics(out, config) {
|
|
|
2327
2465
|
// src/index.ts
|
|
2328
2466
|
init_logger();
|
|
2329
2467
|
|
|
2468
|
+
// src/edit.ts
|
|
2469
|
+
init_collections();
|
|
2470
|
+
init_paths();
|
|
2471
|
+
init_errors();
|
|
2472
|
+
async function resolveEditTarget(target, cwd) {
|
|
2473
|
+
if (target && target.trim()) {
|
|
2474
|
+
const [coll, ...rest] = target.trim().split(":");
|
|
2475
|
+
const slug = rest.join(":");
|
|
2476
|
+
const entry = await findCollection(coll);
|
|
2477
|
+
if (!entry) throw new CollectionNotFoundError(coll);
|
|
2478
|
+
if (slug) {
|
|
2479
|
+
const article = await readArticle(coll, slug);
|
|
2480
|
+
return { path: article.path, kind: "article" };
|
|
2481
|
+
}
|
|
2482
|
+
return { path: entry.path, kind: "collection" };
|
|
2483
|
+
}
|
|
2484
|
+
const cwdColl = await resolveCwdCollection(cwd);
|
|
2485
|
+
if (cwdColl) return { path: cwdColl.path, kind: "collection" };
|
|
2486
|
+
return { path: collectionsDir(), kind: "root" };
|
|
2487
|
+
}
|
|
2488
|
+
async function resolveEditorCommand(flag) {
|
|
2489
|
+
const raw = flag ?? await getConfigValue("editor") ?? process.env.VISUAL ?? process.env.EDITOR;
|
|
2490
|
+
if (!raw || !raw.trim()) return void 0;
|
|
2491
|
+
return raw.trim().split(/\s+/);
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2330
2494
|
// src/plugins/index.ts
|
|
2331
2495
|
init_host();
|
|
2332
2496
|
init_events();
|
|
@@ -2365,6 +2529,7 @@ init_registry2();
|
|
|
2365
2529
|
InvalidTargetError,
|
|
2366
2530
|
NATIVE_ENGINE,
|
|
2367
2531
|
NoDefaultCollectionError,
|
|
2532
|
+
PROJECT_CONFIG_FILE,
|
|
2368
2533
|
PluginError,
|
|
2369
2534
|
REPO_CONFIG_FILE,
|
|
2370
2535
|
WorktreeNotAllowedError,
|
|
@@ -2375,6 +2540,7 @@ init_registry2();
|
|
|
2375
2540
|
buildLinkGraph,
|
|
2376
2541
|
buildNavTree,
|
|
2377
2542
|
buildSearchIndex,
|
|
2543
|
+
buildSiteConfig,
|
|
2378
2544
|
collectionDir,
|
|
2379
2545
|
collectionGitStatus,
|
|
2380
2546
|
collectionMeta,
|
|
@@ -2419,8 +2585,10 @@ init_registry2();
|
|
|
2419
2585
|
readArticle,
|
|
2420
2586
|
readConfig,
|
|
2421
2587
|
readPluginRegistry,
|
|
2588
|
+
readProjectConfig,
|
|
2422
2589
|
readRegistry,
|
|
2423
2590
|
readRepoConfig,
|
|
2591
|
+
readSiteConfig,
|
|
2424
2592
|
registerCollection,
|
|
2425
2593
|
registerPlugin,
|
|
2426
2594
|
registryPath,
|
|
@@ -2434,10 +2602,14 @@ init_registry2();
|
|
|
2434
2602
|
resolveCollectionSelection,
|
|
2435
2603
|
resolveCwdCollection,
|
|
2436
2604
|
resolveDefaultSearchMode,
|
|
2605
|
+
resolveEditTarget,
|
|
2606
|
+
resolveEditorCommand,
|
|
2437
2607
|
resolveHome,
|
|
2438
2608
|
resolveLogLevel,
|
|
2439
2609
|
resolvePage,
|
|
2610
|
+
resolveProjectCollections,
|
|
2440
2611
|
search,
|
|
2612
|
+
seedProjectHome,
|
|
2441
2613
|
serializeArticle,
|
|
2442
2614
|
setConfigValue,
|
|
2443
2615
|
setTags,
|