diffwiki-core 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-MLUJY3HI.js → chunk-7V2DIQNL.js} +4 -3
- package/dist/{events-UCJWB4WP.js → events-AVD3G5AX.js} +1 -1
- package/dist/index.cjs +252 -36
- package/dist/index.d.cts +58 -32
- package/dist/index.d.ts +58 -32
- package/dist/index.js +244 -36
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
upsertPlugin,
|
|
48
48
|
writePluginRegistry,
|
|
49
49
|
writeRegistry
|
|
50
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-7V2DIQNL.js";
|
|
51
51
|
|
|
52
52
|
// src/config.ts
|
|
53
53
|
import fs from "fs/promises";
|
|
@@ -91,12 +91,20 @@ async function getConfigValue(key) {
|
|
|
91
91
|
const value = config[key];
|
|
92
92
|
return typeof value === "string" ? value : void 0;
|
|
93
93
|
}
|
|
94
|
+
async function showSystemEnabled() {
|
|
95
|
+
const config = await readConfig();
|
|
96
|
+
return config.showSystem === true || config.showSystem === "true";
|
|
97
|
+
}
|
|
94
98
|
|
|
95
99
|
// src/slugify.ts
|
|
96
100
|
function slugify(input) {
|
|
97
101
|
const slug = input.toLowerCase().replace(/^#+\s*/, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80).replace(/-+$/g, "");
|
|
98
102
|
return slug.length > 0 ? slug : "untitled";
|
|
99
103
|
}
|
|
104
|
+
function slugifyPath(input) {
|
|
105
|
+
const segs = input.split("/").map((s) => s.trim()).filter((s) => s.length > 0).map((s) => slugify(s));
|
|
106
|
+
return segs.length > 0 ? segs.join("/") : "untitled";
|
|
107
|
+
}
|
|
100
108
|
|
|
101
109
|
// src/frontmatter.ts
|
|
102
110
|
import matter from "gray-matter";
|
|
@@ -110,10 +118,10 @@ var YAML_ENGINE = {
|
|
|
110
118
|
};
|
|
111
119
|
var MATTER_OPTS = { engines: { yaml: YAML_ENGINE } };
|
|
112
120
|
function coerceAudience(value) {
|
|
113
|
-
return value === "private"
|
|
121
|
+
return value === "private" || value === "system" ? value : "public";
|
|
114
122
|
}
|
|
115
123
|
function coerceStatus(value) {
|
|
116
|
-
return value === "draft"
|
|
124
|
+
return value === "draft" || value === "deprecated" ? value : "published";
|
|
117
125
|
}
|
|
118
126
|
function parseFallback(raw) {
|
|
119
127
|
const fenceRe = /^---[ \t]*\n([\s\S]*?)\n---[ \t]*\n/;
|
|
@@ -158,8 +166,8 @@ function parseArticle(raw) {
|
|
|
158
166
|
}
|
|
159
167
|
function serializeArticle(article) {
|
|
160
168
|
const front = { title: article.title, tags: article.tags };
|
|
161
|
-
if (article.audience === "private") front.audience =
|
|
162
|
-
if (article.status === "draft") front.status =
|
|
169
|
+
if (article.audience === "private" || article.audience === "system") front.audience = article.audience;
|
|
170
|
+
if (article.status === "draft" || article.status === "deprecated") front.status = article.status;
|
|
163
171
|
return matter.stringify(article.body, front, MATTER_OPTS);
|
|
164
172
|
}
|
|
165
173
|
|
|
@@ -168,7 +176,7 @@ import fs2 from "fs/promises";
|
|
|
168
176
|
import path from "path";
|
|
169
177
|
async function fireHook(event, collection, relPath) {
|
|
170
178
|
try {
|
|
171
|
-
const { emitPluginEvent: emitPluginEvent2 } = await import("./events-
|
|
179
|
+
const { emitPluginEvent: emitPluginEvent2 } = await import("./events-AVD3G5AX.js");
|
|
172
180
|
await emitPluginEvent2(event, collection, relPath);
|
|
173
181
|
} catch {
|
|
174
182
|
}
|
|
@@ -205,18 +213,15 @@ async function resolveCollectionDir(name) {
|
|
|
205
213
|
}
|
|
206
214
|
function ensureMdPath(file) {
|
|
207
215
|
if (/\.mdx?$/.test(file)) return file;
|
|
208
|
-
|
|
209
|
-
const base = slugify(path.basename(file));
|
|
210
|
-
const rel = dir === "." ? `${base}.md` : path.join(dir, `${base}.md`);
|
|
211
|
-
return rel;
|
|
216
|
+
return `${slugifyPath(file)}.md`;
|
|
212
217
|
}
|
|
213
218
|
async function createArticle(opts) {
|
|
214
219
|
const { collection, title } = parseAddTarget(opts.target);
|
|
215
220
|
const { name, dir } = await resolveCollectionDir(collection);
|
|
216
|
-
const filePath = path.join(dir, `${
|
|
221
|
+
const filePath = path.join(dir, `${slugifyPath(title)}.md`);
|
|
217
222
|
const body = opts.body ?? `# ${title}
|
|
218
223
|
`;
|
|
219
|
-
await fs2.mkdir(
|
|
224
|
+
await fs2.mkdir(path.dirname(filePath), { recursive: true });
|
|
220
225
|
await fs2.writeFile(
|
|
221
226
|
filePath,
|
|
222
227
|
serializeArticle({ title, tags: opts.tags, body, audience: opts.audience, status: opts.status }),
|
|
@@ -562,6 +567,13 @@ function normaliseSite(raw) {
|
|
|
562
567
|
...home !== void 0 ? { home } : {}
|
|
563
568
|
};
|
|
564
569
|
}
|
|
570
|
+
function normaliseContext(raw) {
|
|
571
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
572
|
+
const r = raw;
|
|
573
|
+
if (!Array.isArray(r.collections)) return void 0;
|
|
574
|
+
const collections = r.collections.filter((c) => typeof c === "string");
|
|
575
|
+
return collections.length > 0 ? { collections } : void 0;
|
|
576
|
+
}
|
|
565
577
|
async function readProjectConfig(dir) {
|
|
566
578
|
let raw;
|
|
567
579
|
try {
|
|
@@ -590,7 +602,11 @@ async function readProjectConfig(dir) {
|
|
|
590
602
|
collections.push({ name, path: parsed.path });
|
|
591
603
|
}
|
|
592
604
|
const site = normaliseSite(parsed.site);
|
|
593
|
-
|
|
605
|
+
const context = normaliseContext(parsed.context);
|
|
606
|
+
return { ...site ? { site } : {}, ...context ? { context } : {}, collections };
|
|
607
|
+
}
|
|
608
|
+
async function readContextCollections(dir) {
|
|
609
|
+
return (await readProjectConfig(dir))?.context?.collections ?? [];
|
|
594
610
|
}
|
|
595
611
|
async function resolveProjectCollections(dir) {
|
|
596
612
|
const config = await readProjectConfig(dir);
|
|
@@ -1080,8 +1096,12 @@ function localGraph(graph, focusId, include, siblingCap = 12) {
|
|
|
1080
1096
|
|
|
1081
1097
|
// src/site.ts
|
|
1082
1098
|
var isStatic = () => process.env.DIFFWIKI_STATIC === "1";
|
|
1083
|
-
function toTreeNodes(coll, nodes) {
|
|
1084
|
-
const visible =
|
|
1099
|
+
function toTreeNodes(coll, nodes, showSystem = false) {
|
|
1100
|
+
const visible = nodes.filter((n) => {
|
|
1101
|
+
if (!showSystem && n.audience === "system") return false;
|
|
1102
|
+
if (isStatic() && (n.audience === "private" || n.status === "draft")) return false;
|
|
1103
|
+
return true;
|
|
1104
|
+
});
|
|
1085
1105
|
return visible.map(
|
|
1086
1106
|
(n) => n.children ? {
|
|
1087
1107
|
name: n.name,
|
|
@@ -1089,7 +1109,7 @@ function toTreeNodes(coll, nodes) {
|
|
|
1089
1109
|
path: n.slug ? `/${coll}/${n.slug}` : void 0,
|
|
1090
1110
|
...n.audience ? { audience: n.audience } : {},
|
|
1091
1111
|
...n.status ? { status: n.status } : {},
|
|
1092
|
-
children: toTreeNodes(coll, n.children)
|
|
1112
|
+
children: toTreeNodes(coll, n.children, showSystem)
|
|
1093
1113
|
} : {
|
|
1094
1114
|
name: n.name,
|
|
1095
1115
|
title: n.title ?? n.name,
|
|
@@ -1107,44 +1127,50 @@ async function gitInfo(e) {
|
|
|
1107
1127
|
const s = await collectionGitStatus(e);
|
|
1108
1128
|
return s ? { branch: s.branch, behind: s.hasUpstream ? s.behind : void 0 } : {};
|
|
1109
1129
|
}
|
|
1110
|
-
async function buildCollections() {
|
|
1130
|
+
async function buildCollections(opts) {
|
|
1131
|
+
const showSystem = opts?.showSystem ?? false;
|
|
1111
1132
|
const entries = await listCollections();
|
|
1112
1133
|
const infos = await Promise.all(
|
|
1113
1134
|
entries.map(async (e) => {
|
|
1114
|
-
const
|
|
1135
|
+
const meta = await collectionMeta(e.path);
|
|
1136
|
+
const audience = meta.audience ?? e.audience;
|
|
1115
1137
|
return {
|
|
1116
1138
|
name: e.name,
|
|
1117
1139
|
type: e.type,
|
|
1118
1140
|
path: e.path,
|
|
1119
|
-
...title ? { title } : {},
|
|
1141
|
+
...meta.title ? { title: meta.title } : {},
|
|
1120
1142
|
...await gitInfo(e),
|
|
1121
1143
|
...audience ? { audience } : {},
|
|
1122
|
-
...status ? { status } : {}
|
|
1144
|
+
...meta.status ? { status: meta.status } : {}
|
|
1123
1145
|
};
|
|
1124
1146
|
})
|
|
1125
1147
|
);
|
|
1126
|
-
|
|
1148
|
+
const visible = showSystem ? infos : infos.filter((c) => c.audience !== "system");
|
|
1149
|
+
return isStatic() ? visible.filter((c) => c.audience !== "private" && c.status !== "draft") : visible;
|
|
1127
1150
|
}
|
|
1128
|
-
async function buildNavTree() {
|
|
1151
|
+
async function buildNavTree(opts) {
|
|
1152
|
+
const showSystem = opts?.showSystem ?? false;
|
|
1129
1153
|
const entries = await listCollections();
|
|
1130
1154
|
const trees = await Promise.all(
|
|
1131
1155
|
entries.map(async (e) => {
|
|
1132
|
-
const
|
|
1156
|
+
const meta = await collectionMeta(e.path);
|
|
1157
|
+
const audience = meta.audience ?? e.audience;
|
|
1133
1158
|
return {
|
|
1134
1159
|
collection: {
|
|
1135
1160
|
name: e.name,
|
|
1136
1161
|
type: e.type,
|
|
1137
1162
|
path: e.path,
|
|
1138
|
-
...title ? { title } : {},
|
|
1163
|
+
...meta.title ? { title: meta.title } : {},
|
|
1139
1164
|
...await gitInfo(e),
|
|
1140
1165
|
...audience ? { audience } : {},
|
|
1141
|
-
...status ? { status } : {}
|
|
1166
|
+
...meta.status ? { status: meta.status } : {}
|
|
1142
1167
|
},
|
|
1143
|
-
nodes: toTreeNodes(e.name, await listArticleTree(e.name))
|
|
1168
|
+
nodes: toTreeNodes(e.name, await listArticleTree(e.name), showSystem)
|
|
1144
1169
|
};
|
|
1145
1170
|
})
|
|
1146
1171
|
);
|
|
1147
|
-
|
|
1172
|
+
const visible = showSystem ? trees : trees.filter((t) => t.collection.audience !== "system");
|
|
1173
|
+
return isStatic() ? visible.filter((t) => t.collection.audience !== "private" && t.collection.status !== "draft") : visible;
|
|
1148
1174
|
}
|
|
1149
1175
|
function findNode(nodes, slug) {
|
|
1150
1176
|
for (const n of nodes) {
|
|
@@ -1267,12 +1293,20 @@ async function collectMarkdownFiles(dir) {
|
|
|
1267
1293
|
}
|
|
1268
1294
|
return files;
|
|
1269
1295
|
}
|
|
1270
|
-
|
|
1296
|
+
function normalizeCollections(collection) {
|
|
1297
|
+
if (collection === void 0) return [];
|
|
1298
|
+
const names = Array.isArray(collection) ? collection : [collection];
|
|
1299
|
+
return names.map((n) => n.trim()).filter(Boolean);
|
|
1300
|
+
}
|
|
1301
|
+
async function nativeSearch(term, collections, collection, hideSystem = false) {
|
|
1271
1302
|
let targets = collections;
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1303
|
+
const names = normalizeCollections(collection);
|
|
1304
|
+
if (names.length > 0) {
|
|
1305
|
+
for (const n of names) {
|
|
1306
|
+
if (!collections.some((c) => c.name === n)) throw new CollectionNotFoundError(n);
|
|
1307
|
+
}
|
|
1308
|
+
const wanted = new Set(names);
|
|
1309
|
+
targets = collections.filter((c) => wanted.has(c.name));
|
|
1276
1310
|
}
|
|
1277
1311
|
const docs = [];
|
|
1278
1312
|
for (const entry of targets) {
|
|
@@ -1283,6 +1317,7 @@ async function nativeSearch(term, collections, collection) {
|
|
|
1283
1317
|
let body = "";
|
|
1284
1318
|
try {
|
|
1285
1319
|
const parsed = parseArticle(await fs7.readFile(full, "utf8"));
|
|
1320
|
+
if (hideSystem && parsed.audience === "system") continue;
|
|
1286
1321
|
if (parsed.title) title = parsed.title;
|
|
1287
1322
|
tags = parsed.tags;
|
|
1288
1323
|
body = parsed.body;
|
|
@@ -1335,28 +1370,49 @@ async function resolveDefaultSearchMode() {
|
|
|
1335
1370
|
}
|
|
1336
1371
|
return { engine, type: searchTypes[0] };
|
|
1337
1372
|
}
|
|
1373
|
+
async function dropSystemHits(hits) {
|
|
1374
|
+
const kept = [];
|
|
1375
|
+
for (const hit of hits) {
|
|
1376
|
+
try {
|
|
1377
|
+
if (parseArticle(await fs7.readFile(hit.path, "utf8")).audience === "system") continue;
|
|
1378
|
+
} catch {
|
|
1379
|
+
}
|
|
1380
|
+
kept.push(hit);
|
|
1381
|
+
}
|
|
1382
|
+
return kept;
|
|
1383
|
+
}
|
|
1338
1384
|
async function search(term, opts) {
|
|
1339
1385
|
const collections = await listCollections();
|
|
1386
|
+
const wanted = normalizeCollections(opts?.collection);
|
|
1387
|
+
for (const n of wanted) {
|
|
1388
|
+
if (!collections.some((c) => c.name === n)) throw new CollectionNotFoundError(n);
|
|
1389
|
+
}
|
|
1390
|
+
const showSystem = opts?.showSystem ?? await showSystemEnabled();
|
|
1391
|
+
const hideSystem = !showSystem && wanted.length === 0;
|
|
1392
|
+
const searchable = hideSystem ? collections.filter((c) => c.audience !== "system") : collections;
|
|
1393
|
+
const filterHits = (hits) => wanted.length > 0 ? hits.filter((h) => wanted.includes(h.collection)) : hits;
|
|
1340
1394
|
const engine = opts?.engine ?? await resolveDefaultEngine();
|
|
1341
1395
|
log2.debug(`resolved search engine "${engine}"`);
|
|
1342
1396
|
if (engine === NATIVE_ENGINE) {
|
|
1343
|
-
const hits = await nativeSearch(term,
|
|
1397
|
+
const hits = await nativeSearch(term, searchable, opts?.collection, hideSystem);
|
|
1344
1398
|
return { hits, engine, fellBackToNative: false };
|
|
1345
1399
|
}
|
|
1346
1400
|
const provider = await loadSearchProvider(engine);
|
|
1347
1401
|
if (!provider) {
|
|
1348
1402
|
const error = `no provider found for engine "${engine}"`;
|
|
1349
1403
|
log2.warn(`engine "${engine}" unavailable, falling back to native: ${error}`);
|
|
1350
|
-
const hits = await nativeSearch(term,
|
|
1404
|
+
const hits = await nativeSearch(term, searchable, opts?.collection, hideSystem);
|
|
1351
1405
|
return { hits, engine, fellBackToNative: true, error };
|
|
1352
1406
|
}
|
|
1353
1407
|
try {
|
|
1354
|
-
const
|
|
1408
|
+
const providerOpts = { ...opts, collection: wanted.length === 1 ? wanted[0] : void 0 };
|
|
1409
|
+
const raw = filterHits(await provider.handleSearch(term, searchable, providerOpts));
|
|
1410
|
+
const hits = hideSystem ? await dropSystemHits(raw) : raw;
|
|
1355
1411
|
return { hits, engine, fellBackToNative: false };
|
|
1356
1412
|
} catch (err) {
|
|
1357
1413
|
const message = err instanceof Error ? err.message : String(err);
|
|
1358
1414
|
log2.warn(`engine "${engine}" failed, falling back to native: ${message}`);
|
|
1359
|
-
const hits = await nativeSearch(term,
|
|
1415
|
+
const hits = await nativeSearch(term, searchable, opts?.collection, hideSystem);
|
|
1360
1416
|
return { hits, engine, fellBackToNative: true, error: message };
|
|
1361
1417
|
}
|
|
1362
1418
|
}
|
|
@@ -1380,6 +1436,7 @@ async function readArticleParts(collDir, slug) {
|
|
|
1380
1436
|
}
|
|
1381
1437
|
async function flatten(coll, collDir, nodes, out) {
|
|
1382
1438
|
for (const n of nodes) {
|
|
1439
|
+
if (n.audience === "system") continue;
|
|
1383
1440
|
if (isStatic2() && (n.audience === "private" || n.status === "draft")) continue;
|
|
1384
1441
|
if (n.slug && !n.children) {
|
|
1385
1442
|
const title = n.title ?? n.name;
|
|
@@ -1407,6 +1464,7 @@ async function buildSearchIndex(collection) {
|
|
|
1407
1464
|
try {
|
|
1408
1465
|
const collEntry = await findCollection(e.name);
|
|
1409
1466
|
const collDir = path8.resolve(collEntry?.path ?? e.path);
|
|
1467
|
+
if (collEntry?.audience === "system") continue;
|
|
1410
1468
|
if (isStatic2()) {
|
|
1411
1469
|
const cm = await collectionMeta(collDir);
|
|
1412
1470
|
if (cm.audience === "private" || cm.status === "draft") continue;
|
|
@@ -1739,6 +1797,151 @@ async function appendPluginDiagnostics(out, config) {
|
|
|
1739
1797
|
}
|
|
1740
1798
|
}
|
|
1741
1799
|
|
|
1800
|
+
// src/lint.ts
|
|
1801
|
+
import fs11 from "fs/promises";
|
|
1802
|
+
import path10 from "path";
|
|
1803
|
+
var BUDGETS = {
|
|
1804
|
+
recipe: { min: 300, max: 1e3 },
|
|
1805
|
+
"recipe-steps": { min: 500, max: 1500 },
|
|
1806
|
+
guide: { min: 1500, max: 5e3 },
|
|
1807
|
+
"system-design": { min: 2e3, max: 6e3 },
|
|
1808
|
+
"component-design": { min: 1e3, max: 3e3 },
|
|
1809
|
+
// Fallback for a bare `architecture` tag when system vs component isn't declared.
|
|
1810
|
+
architecture: { min: 1e3, max: 6e3 }
|
|
1811
|
+
};
|
|
1812
|
+
function estimateTokens(body) {
|
|
1813
|
+
return Math.ceil(body.trim().length / 4);
|
|
1814
|
+
}
|
|
1815
|
+
function inferShape(tags, body) {
|
|
1816
|
+
const t = new Set(tags.map((x) => x.toLowerCase()));
|
|
1817
|
+
if (t.has("guide")) return "guide";
|
|
1818
|
+
if (t.has("component-design") || t.has("component")) return "component-design";
|
|
1819
|
+
if (t.has("system-design") || t.has("system")) return "system-design";
|
|
1820
|
+
if (t.has("architecture")) return "architecture";
|
|
1821
|
+
if (/^###\s+Step\b/im.test(body)) return "recipe-steps";
|
|
1822
|
+
return "recipe";
|
|
1823
|
+
}
|
|
1824
|
+
function lintArticle(article) {
|
|
1825
|
+
const findings = [];
|
|
1826
|
+
const { title, tags, status, body } = article;
|
|
1827
|
+
const shape = inferShape(tags, body);
|
|
1828
|
+
const tokens = estimateTokens(body);
|
|
1829
|
+
const budget = BUDGETS[shape];
|
|
1830
|
+
if (!title || !title.trim()) {
|
|
1831
|
+
findings.push({ level: "error", rule: "frontmatter-title", message: "missing frontmatter title" });
|
|
1832
|
+
}
|
|
1833
|
+
if (tags.map((x) => x.toLowerCase()).includes("diffwiki-template")) {
|
|
1834
|
+
findings.push({
|
|
1835
|
+
level: "error",
|
|
1836
|
+
rule: "template-tag-leak",
|
|
1837
|
+
message: "instance still tagged diffwiki-template"
|
|
1838
|
+
});
|
|
1839
|
+
}
|
|
1840
|
+
if (tags.length === 0) {
|
|
1841
|
+
findings.push({ level: "warn", rule: "no-tags", message: "no tags -- add topic tags for recall" });
|
|
1842
|
+
}
|
|
1843
|
+
const hasRefsSection = /^##\s+References\b/im.test(body);
|
|
1844
|
+
const hasInlineLink = /\]\([^)]+\)/.test(body);
|
|
1845
|
+
if (!hasRefsSection && !hasInlineLink) {
|
|
1846
|
+
findings.push({
|
|
1847
|
+
level: "error",
|
|
1848
|
+
rule: "no-references",
|
|
1849
|
+
message: "has no ## References section and no inline links"
|
|
1850
|
+
});
|
|
1851
|
+
}
|
|
1852
|
+
if ((shape === "guide" || shape === "architecture") && !hasRefsSection) {
|
|
1853
|
+
findings.push({
|
|
1854
|
+
level: "warn",
|
|
1855
|
+
rule: "missing-references-section",
|
|
1856
|
+
message: `${shape} should end with a ## References section`
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
if (tokens > budget.max) {
|
|
1860
|
+
findings.push({
|
|
1861
|
+
level: "error",
|
|
1862
|
+
rule: "token-budget",
|
|
1863
|
+
message: `body ~${tokens} tokens exceeds ${shape} budget (${budget.max})`
|
|
1864
|
+
});
|
|
1865
|
+
} else if (tokens < budget.min) {
|
|
1866
|
+
findings.push({
|
|
1867
|
+
level: "warn",
|
|
1868
|
+
rule: "token-thin",
|
|
1869
|
+
message: `body ~${tokens} tokens is thin for ${shape} (min ${budget.min})`
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
if (status === "draft") {
|
|
1873
|
+
findings.push({ level: "warn", rule: "status-draft", message: "status is still draft" });
|
|
1874
|
+
}
|
|
1875
|
+
if (/\*\*Load when:\*\*/i.test(body)) {
|
|
1876
|
+
findings.push({
|
|
1877
|
+
level: "warn",
|
|
1878
|
+
rule: "load-when-prefix",
|
|
1879
|
+
message: "remove the **Load when:** prefix (implicit trigger)"
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
if (/^[A-Za-z0-9][\w .+-]*:\s/.test(title)) {
|
|
1883
|
+
findings.push({ level: "warn", rule: "title-prefix", message: `title "${title}" repeats a path-like prefix` });
|
|
1884
|
+
}
|
|
1885
|
+
return findings;
|
|
1886
|
+
}
|
|
1887
|
+
async function walkMarkdown(dir) {
|
|
1888
|
+
let entries;
|
|
1889
|
+
try {
|
|
1890
|
+
entries = await fs11.readdir(dir, { withFileTypes: true });
|
|
1891
|
+
} catch {
|
|
1892
|
+
return [];
|
|
1893
|
+
}
|
|
1894
|
+
const out = [];
|
|
1895
|
+
for (const e of entries) {
|
|
1896
|
+
const full = path10.join(dir, e.name);
|
|
1897
|
+
if (e.isDirectory()) out.push(...await walkMarkdown(full));
|
|
1898
|
+
else if (e.isFile() && /\.mdx?$/.test(e.name) && !/-template\.mdx?$/.test(e.name)) out.push(full);
|
|
1899
|
+
}
|
|
1900
|
+
return out;
|
|
1901
|
+
}
|
|
1902
|
+
async function resolveScope(scope) {
|
|
1903
|
+
const all = await listCollections();
|
|
1904
|
+
if (scope === "all") return all.filter((c) => c.audience !== "system");
|
|
1905
|
+
if (scope === "current") {
|
|
1906
|
+
const cur = await resolveCwdCollection(process.cwd());
|
|
1907
|
+
if (!cur) throw new Error("No collection for the current directory -- pass -c <name> or -c all.");
|
|
1908
|
+
return [cur];
|
|
1909
|
+
}
|
|
1910
|
+
const names = scope.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1911
|
+
const chosen = [];
|
|
1912
|
+
for (const n of names) {
|
|
1913
|
+
const hit = all.find((c) => c.name === n);
|
|
1914
|
+
if (!hit) throw new CollectionNotFoundError(n);
|
|
1915
|
+
chosen.push(hit);
|
|
1916
|
+
}
|
|
1917
|
+
return chosen;
|
|
1918
|
+
}
|
|
1919
|
+
async function lintCollections(scope) {
|
|
1920
|
+
const collections = await resolveScope(scope);
|
|
1921
|
+
const files = [];
|
|
1922
|
+
let errorCount = 0;
|
|
1923
|
+
let warnCount = 0;
|
|
1924
|
+
for (const coll of collections) {
|
|
1925
|
+
for (const full of await walkMarkdown(coll.path)) {
|
|
1926
|
+
const rel = path10.relative(coll.path, full);
|
|
1927
|
+
const label = `${coll.name}/${rel}`;
|
|
1928
|
+
let findings = [];
|
|
1929
|
+
try {
|
|
1930
|
+
const parsed = parseArticle(await fs11.readFile(full, "utf8"));
|
|
1931
|
+
findings = lintArticle(parsed);
|
|
1932
|
+
} catch (err) {
|
|
1933
|
+
findings = [{ level: "error", rule: "parse", message: `could not parse: ${err.message}` }];
|
|
1934
|
+
}
|
|
1935
|
+
for (const f of findings) {
|
|
1936
|
+
if (f.level === "error") errorCount++;
|
|
1937
|
+
else warnCount++;
|
|
1938
|
+
}
|
|
1939
|
+
files.push({ path: label, findings });
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
return { files, fileCount: files.length, errorCount, warnCount };
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1742
1945
|
// src/edit.ts
|
|
1743
1946
|
async function resolveEditTarget(target, cwd) {
|
|
1744
1947
|
if (target && target.trim()) {
|
|
@@ -1829,6 +2032,8 @@ export {
|
|
|
1829
2032
|
installPlugin,
|
|
1830
2033
|
invalidateLinkGraph,
|
|
1831
2034
|
isLinkedWorktree,
|
|
2035
|
+
lintArticle,
|
|
2036
|
+
lintCollections,
|
|
1832
2037
|
listArticleTree,
|
|
1833
2038
|
listAvailablePlugins,
|
|
1834
2039
|
listCollections,
|
|
@@ -1847,6 +2052,7 @@ export {
|
|
|
1847
2052
|
rankDocs,
|
|
1848
2053
|
readArticle,
|
|
1849
2054
|
readConfig,
|
|
2055
|
+
readContextCollections,
|
|
1850
2056
|
readPluginRegistry,
|
|
1851
2057
|
readProjectConfig,
|
|
1852
2058
|
readRegistry,
|
|
@@ -1877,7 +2083,9 @@ export {
|
|
|
1877
2083
|
setArticleMeta,
|
|
1878
2084
|
setConfigValue,
|
|
1879
2085
|
setTags,
|
|
2086
|
+
showSystemEnabled,
|
|
1880
2087
|
slugify,
|
|
2088
|
+
slugifyPath,
|
|
1881
2089
|
toTreeNodes,
|
|
1882
2090
|
tokenize,
|
|
1883
2091
|
unregisterCollection,
|