@silicajs/core 0.3.1 → 0.5.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/README.md +1 -1
- package/dist/{chunk-7KWXP3FM.js → chunk-BEINUFYU.js} +47 -80
- package/dist/chunk-BEINUFYU.js.map +1 -0
- package/dist/chunk-HFUUGJO6.js +68 -0
- package/dist/chunk-HFUUGJO6.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +652 -85
- package/dist/index.js.map +1 -1
- package/dist/precompute-worker.d.ts +2 -0
- package/dist/precompute-worker.js +41 -0
- package/dist/precompute-worker.js.map +1 -0
- package/dist/runtime.d.ts +2 -2
- package/dist/runtime.js +14 -4
- package/dist/{theme-DF-R1di4.d.ts → theme-BK5ZRXFG.d.ts} +78 -4
- package/dist/theme.d.ts +1 -1
- package/package.json +3 -2
- package/dist/chunk-7KWXP3FM.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatPropertyLabel,
|
|
3
|
+
formatPropertyValue,
|
|
4
|
+
getMenuLabel,
|
|
5
|
+
getPageProperties
|
|
6
|
+
} from "./chunk-HFUUGJO6.js";
|
|
1
7
|
import {
|
|
2
8
|
analyzeMarkdown,
|
|
3
9
|
asFilePath,
|
|
4
10
|
asFullSlug,
|
|
5
11
|
asRelativeURL,
|
|
6
12
|
asSimpleSlug,
|
|
7
|
-
|
|
8
|
-
formatPropertyValue,
|
|
13
|
+
createWikiLinkResolutionIndex,
|
|
9
14
|
generateDescriptionFromContent,
|
|
10
15
|
getDescription,
|
|
11
|
-
getMenuLabel,
|
|
12
16
|
getMetaDescription,
|
|
13
|
-
getPageProperties,
|
|
14
17
|
getTitle,
|
|
15
18
|
hasNumericPrefixInPath,
|
|
16
19
|
hrefToSlug,
|
|
@@ -29,7 +32,7 @@ import {
|
|
|
29
32
|
slugifySegment,
|
|
30
33
|
stripNumericPrefix,
|
|
31
34
|
tagToHref
|
|
32
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-BEINUFYU.js";
|
|
33
36
|
|
|
34
37
|
// src/config.ts
|
|
35
38
|
import path from "path";
|
|
@@ -103,9 +106,30 @@ function resolveConfig(config = {}, projectRoot = process.cwd()) {
|
|
|
103
106
|
filters: {
|
|
104
107
|
removeDrafts: config.filters?.removeDrafts ?? true,
|
|
105
108
|
explicitPublish: config.filters?.explicitPublish ?? false
|
|
109
|
+
},
|
|
110
|
+
render: {
|
|
111
|
+
prerender: resolvePrerenderConfig(config.render?.prerender),
|
|
112
|
+
cache: {
|
|
113
|
+
storage: config.render?.cache?.storage ?? "filesystem",
|
|
114
|
+
directory: config.render?.cache?.directory
|
|
115
|
+
}
|
|
106
116
|
}
|
|
107
117
|
};
|
|
108
118
|
}
|
|
119
|
+
function resolvePrerenderConfig(prerender) {
|
|
120
|
+
if (!prerender || prerender === "all") return { strategy: "all" };
|
|
121
|
+
if (prerender === "none") return { strategy: "none" };
|
|
122
|
+
if ("strategy" in prerender && prerender.strategy === "all") {
|
|
123
|
+
return { ...prerender, strategy: "all" };
|
|
124
|
+
}
|
|
125
|
+
if ("strategy" in prerender && prerender.strategy === "none") {
|
|
126
|
+
return { ...prerender, strategy: "none" };
|
|
127
|
+
}
|
|
128
|
+
if ("strategy" in prerender && prerender.strategy === "custom") {
|
|
129
|
+
return { ...prerender, strategy: "custom" };
|
|
130
|
+
}
|
|
131
|
+
return { ...prerender, strategy: "depth" };
|
|
132
|
+
}
|
|
109
133
|
|
|
110
134
|
// src/files.ts
|
|
111
135
|
import path2 from "path";
|
|
@@ -169,47 +193,312 @@ function isMarkdownFile(filePath) {
|
|
|
169
193
|
// src/precompute.ts
|
|
170
194
|
import crypto from "crypto";
|
|
171
195
|
import { execFile } from "child_process";
|
|
172
|
-
import
|
|
196
|
+
import os from "os";
|
|
197
|
+
import path4 from "path";
|
|
173
198
|
import { promisify } from "util";
|
|
199
|
+
import { Worker } from "worker_threads";
|
|
200
|
+
import fs3 from "fs-extra";
|
|
201
|
+
|
|
202
|
+
// src/vault-db.ts
|
|
203
|
+
import path3 from "path";
|
|
204
|
+
import Database from "better-sqlite3";
|
|
174
205
|
import fs2 from "fs-extra";
|
|
175
206
|
import {
|
|
176
|
-
|
|
177
|
-
|
|
207
|
+
buildSearchTables,
|
|
208
|
+
makeExcerpt
|
|
178
209
|
} from "@silicajs/search";
|
|
210
|
+
var VAULT_DATABASE_FILENAME = "vault.db";
|
|
211
|
+
var VAULT_DATABASE_VERSION = "1";
|
|
212
|
+
async function writeVaultDatabase(projectRoot, input) {
|
|
213
|
+
const silicaRoot = path3.join(projectRoot, ".silica");
|
|
214
|
+
await fs2.ensureDir(silicaRoot);
|
|
215
|
+
const databasePath = path3.join(silicaRoot, VAULT_DATABASE_FILENAME);
|
|
216
|
+
const temporaryPath = path3.join(silicaRoot, `${VAULT_DATABASE_FILENAME}.tmp`);
|
|
217
|
+
await removeDatabaseFiles(temporaryPath);
|
|
218
|
+
const db = new Database(temporaryPath);
|
|
219
|
+
try {
|
|
220
|
+
db.pragma("journal_mode = DELETE");
|
|
221
|
+
db.pragma("foreign_keys = ON");
|
|
222
|
+
db.pragma("synchronous = OFF");
|
|
223
|
+
createVaultDatabaseSchema(db);
|
|
224
|
+
populateVaultDatabase(db, input);
|
|
225
|
+
db.exec("VACUUM");
|
|
226
|
+
} finally {
|
|
227
|
+
db.close();
|
|
228
|
+
}
|
|
229
|
+
await removeDatabaseFiles(databasePath);
|
|
230
|
+
await fs2.rename(temporaryPath, databasePath);
|
|
231
|
+
await removeDatabaseSidecars(temporaryPath);
|
|
232
|
+
return databasePath;
|
|
233
|
+
}
|
|
234
|
+
function createVaultDatabaseSchema(db) {
|
|
235
|
+
db.exec(`
|
|
236
|
+
CREATE TABLE vault_metadata (
|
|
237
|
+
key TEXT PRIMARY KEY,
|
|
238
|
+
value TEXT NOT NULL
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
CREATE TABLE notes (
|
|
242
|
+
slug TEXT PRIMARY KEY,
|
|
243
|
+
file TEXT NOT NULL,
|
|
244
|
+
relative_file TEXT NOT NULL,
|
|
245
|
+
title TEXT NOT NULL,
|
|
246
|
+
menu_label TEXT NOT NULL,
|
|
247
|
+
description TEXT,
|
|
248
|
+
generated_description TEXT,
|
|
249
|
+
frontmatter_json TEXT NOT NULL,
|
|
250
|
+
tags_json TEXT NOT NULL,
|
|
251
|
+
search_excerpt TEXT NOT NULL DEFAULT '',
|
|
252
|
+
created TEXT,
|
|
253
|
+
modified TEXT,
|
|
254
|
+
sort_key TEXT,
|
|
255
|
+
listed INTEGER NOT NULL,
|
|
256
|
+
content_hash TEXT NOT NULL,
|
|
257
|
+
render_hash TEXT NOT NULL,
|
|
258
|
+
prerender INTEGER NOT NULL
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
CREATE TABLE note_tags (
|
|
262
|
+
slug TEXT NOT NULL,
|
|
263
|
+
tag TEXT NOT NULL,
|
|
264
|
+
PRIMARY KEY (slug, tag),
|
|
265
|
+
FOREIGN KEY (slug) REFERENCES notes(slug) ON DELETE CASCADE
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
CREATE TABLE links (
|
|
269
|
+
source_slug TEXT NOT NULL,
|
|
270
|
+
target_slug TEXT NOT NULL,
|
|
271
|
+
kind TEXT NOT NULL CHECK (kind IN ('link', 'embed')),
|
|
272
|
+
PRIMARY KEY (source_slug, target_slug, kind)
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
CREATE TABLE broken_links (
|
|
276
|
+
source_slug TEXT NOT NULL,
|
|
277
|
+
target TEXT NOT NULL
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
CREATE TABLE slug_aliases (
|
|
281
|
+
strategy_key TEXT NOT NULL,
|
|
282
|
+
alias TEXT NOT NULL,
|
|
283
|
+
slug TEXT NOT NULL,
|
|
284
|
+
sort_key TEXT,
|
|
285
|
+
PRIMARY KEY (strategy_key, alias, slug)
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
CREATE INDEX notes_prerender_idx ON notes(prerender, slug);
|
|
289
|
+
CREATE INDEX notes_listed_sort_idx ON notes(listed, sort_key, slug);
|
|
290
|
+
CREATE INDEX links_target_idx ON links(target_slug, kind, source_slug);
|
|
291
|
+
CREATE INDEX links_source_idx ON links(source_slug, kind, target_slug);
|
|
292
|
+
CREATE INDEX note_tags_tag_idx ON note_tags(tag, slug);
|
|
293
|
+
CREATE INDEX slug_aliases_lookup_idx
|
|
294
|
+
ON slug_aliases(strategy_key, alias, sort_key, slug);
|
|
295
|
+
`);
|
|
296
|
+
}
|
|
297
|
+
function populateVaultDatabase(db, input) {
|
|
298
|
+
const prerenderSlugs = new Set(input.prerender.slugs);
|
|
299
|
+
const searchBySlug = new Map(
|
|
300
|
+
input.searchRecords.map((record) => [record.slug, record])
|
|
301
|
+
);
|
|
302
|
+
const insertMetadata = db.prepare(`
|
|
303
|
+
INSERT INTO vault_metadata (key, value) VALUES (?, ?)
|
|
304
|
+
`);
|
|
305
|
+
const insertNote = db.prepare(`
|
|
306
|
+
INSERT INTO notes (
|
|
307
|
+
slug,
|
|
308
|
+
file,
|
|
309
|
+
relative_file,
|
|
310
|
+
title,
|
|
311
|
+
menu_label,
|
|
312
|
+
description,
|
|
313
|
+
generated_description,
|
|
314
|
+
frontmatter_json,
|
|
315
|
+
tags_json,
|
|
316
|
+
search_excerpt,
|
|
317
|
+
created,
|
|
318
|
+
modified,
|
|
319
|
+
sort_key,
|
|
320
|
+
listed,
|
|
321
|
+
content_hash,
|
|
322
|
+
render_hash,
|
|
323
|
+
prerender
|
|
324
|
+
)
|
|
325
|
+
VALUES (
|
|
326
|
+
@slug,
|
|
327
|
+
@file,
|
|
328
|
+
@relativeFile,
|
|
329
|
+
@title,
|
|
330
|
+
@menuLabel,
|
|
331
|
+
@description,
|
|
332
|
+
@generatedDescription,
|
|
333
|
+
@frontmatterJson,
|
|
334
|
+
@tagsJson,
|
|
335
|
+
@searchExcerpt,
|
|
336
|
+
@created,
|
|
337
|
+
@modified,
|
|
338
|
+
@sortKey,
|
|
339
|
+
@listed,
|
|
340
|
+
@contentHash,
|
|
341
|
+
@renderHash,
|
|
342
|
+
@prerender
|
|
343
|
+
)
|
|
344
|
+
`);
|
|
345
|
+
const insertTag = db.prepare(`
|
|
346
|
+
INSERT OR IGNORE INTO note_tags (slug, tag) VALUES (?, ?)
|
|
347
|
+
`);
|
|
348
|
+
const insertLink = db.prepare(`
|
|
349
|
+
INSERT OR IGNORE INTO links (source_slug, target_slug, kind)
|
|
350
|
+
VALUES (?, ?, ?)
|
|
351
|
+
`);
|
|
352
|
+
const insertBrokenLink = db.prepare(`
|
|
353
|
+
INSERT INTO broken_links (source_slug, target) VALUES (?, ?)
|
|
354
|
+
`);
|
|
355
|
+
const insertAlias = db.prepare(`
|
|
356
|
+
INSERT OR IGNORE INTO slug_aliases (strategy_key, alias, slug, sort_key)
|
|
357
|
+
VALUES (?, ?, ?, ?)
|
|
358
|
+
`);
|
|
359
|
+
const insertAll = db.transaction(() => {
|
|
360
|
+
insertMetadata.run("version", VAULT_DATABASE_VERSION);
|
|
361
|
+
insertMetadata.run("generatedAt", input.manifest.generatedAt);
|
|
362
|
+
insertMetadata.run("contentDir", input.manifest.contentDir);
|
|
363
|
+
insertMetadata.run("configJson", JSON.stringify(input.config));
|
|
364
|
+
insertMetadata.run(
|
|
365
|
+
"renderEnvironmentHash",
|
|
366
|
+
input.cacheState.renderEnvironmentHash
|
|
367
|
+
);
|
|
368
|
+
insertMetadata.run("configHash", input.cacheState.configHash);
|
|
369
|
+
insertMetadata.run("navigationHash", input.cacheState.navigationHash);
|
|
370
|
+
insertMetadata.run("tagIndexHash", input.cacheState.tagIndexHash);
|
|
371
|
+
insertMetadata.run("rendererVersion", input.cacheState.rendererVersion);
|
|
372
|
+
insertMetadata.run("cacheStateJson", JSON.stringify(input.cacheState));
|
|
373
|
+
for (const entry of input.manifest.entries) {
|
|
374
|
+
const searchRecord = searchBySlug.get(entry.slug);
|
|
375
|
+
insertNote.run({
|
|
376
|
+
slug: entry.slug,
|
|
377
|
+
file: entry.file,
|
|
378
|
+
relativeFile: entry.relativeFile,
|
|
379
|
+
title: entry.title,
|
|
380
|
+
menuLabel: entry.menuLabel,
|
|
381
|
+
description: entry.description,
|
|
382
|
+
generatedDescription: entry.generatedDescription,
|
|
383
|
+
frontmatterJson: JSON.stringify(entry.frontmatter),
|
|
384
|
+
tagsJson: JSON.stringify(entry.tags),
|
|
385
|
+
searchExcerpt: searchRecord ? makeExcerpt(
|
|
386
|
+
searchRecord.content,
|
|
387
|
+
searchRecord.description ?? searchRecord.title
|
|
388
|
+
) : "",
|
|
389
|
+
created: entry.created,
|
|
390
|
+
modified: entry.modified,
|
|
391
|
+
sortKey: entry.sortKey,
|
|
392
|
+
listed: isListedEntry(entry) ? 1 : 0,
|
|
393
|
+
contentHash: entry.contentHash,
|
|
394
|
+
renderHash: input.renderHashes[entry.slug] ?? "missing",
|
|
395
|
+
prerender: prerenderSlugs.has(entry.slug) ? 1 : 0
|
|
396
|
+
});
|
|
397
|
+
for (const tag of entry.tags) {
|
|
398
|
+
for (const hierarchyTag of tagHierarchy(tag)) {
|
|
399
|
+
insertTag.run(entry.slug, hierarchyTag);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
for (const [source, targets] of Object.entries(input.graph.links)) {
|
|
404
|
+
for (const target of targets) {
|
|
405
|
+
insertLink.run(source, target, "link");
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
for (const entry of input.manifest.entries) {
|
|
409
|
+
for (const target of entry.embeds) {
|
|
410
|
+
insertLink.run(entry.slug, target, "embed");
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
for (const brokenLink of input.graph.brokenLinks) {
|
|
414
|
+
insertBrokenLink.run(brokenLink.source, brokenLink.target);
|
|
415
|
+
}
|
|
416
|
+
for (const entry of input.manifest.entries) {
|
|
417
|
+
for (const [strategy, alias] of makeSlugAliases(entry.slug)) {
|
|
418
|
+
insertAlias.run(
|
|
419
|
+
strategy,
|
|
420
|
+
alias,
|
|
421
|
+
entry.slug,
|
|
422
|
+
entry.sortKey ?? entry.slug
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
insertAll();
|
|
428
|
+
buildSearchTables(db, input.searchRecords);
|
|
429
|
+
}
|
|
430
|
+
function isListedEntry(entry) {
|
|
431
|
+
return entry.frontmatter.listed !== false;
|
|
432
|
+
}
|
|
433
|
+
function tagHierarchy(tag) {
|
|
434
|
+
const normalized = tag.trim().replace(/^#/, "").toLowerCase();
|
|
435
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
436
|
+
return segments.map((_, index) => segments.slice(0, index + 1).join("/"));
|
|
437
|
+
}
|
|
438
|
+
function makeSlugAliases(slug) {
|
|
439
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
440
|
+
const simplified = slug === "index" ? "" : slug.replace(/\/index$/, "");
|
|
441
|
+
const basename = simplified.split("/").at(-1) ?? "";
|
|
442
|
+
aliases.set(`absolute:${slug}`, slug);
|
|
443
|
+
if (simplified) aliases.set(`absolute:${simplified}`, simplified);
|
|
444
|
+
if (basename) aliases.set(`shortest:${basename}`, basename);
|
|
445
|
+
return [...aliases.entries()].map(([key, alias]) => [
|
|
446
|
+
key.split(":")[0] ?? "shortest",
|
|
447
|
+
alias
|
|
448
|
+
]);
|
|
449
|
+
}
|
|
450
|
+
async function removeDatabaseFiles(databasePath) {
|
|
451
|
+
await fs2.remove(databasePath);
|
|
452
|
+
await removeDatabaseSidecars(databasePath);
|
|
453
|
+
}
|
|
454
|
+
async function removeDatabaseSidecars(databasePath) {
|
|
455
|
+
await Promise.all([
|
|
456
|
+
fs2.remove(`${databasePath}-wal`),
|
|
457
|
+
fs2.remove(`${databasePath}-shm`),
|
|
458
|
+
fs2.remove(`${databasePath}-journal`)
|
|
459
|
+
]);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// src/precompute.ts
|
|
179
463
|
var execFileAsync = promisify(execFile);
|
|
464
|
+
var MIN_PARALLEL_ANALYSIS_FILES = 64;
|
|
465
|
+
var ANALYSIS_BATCH_SIZE = 16;
|
|
466
|
+
var MAX_ANALYSIS_WORKERS = 12;
|
|
467
|
+
var RENDER_CACHE_SCHEMA_VERSION = "silica-render-v1";
|
|
180
468
|
async function precompute(options = {}) {
|
|
181
469
|
const projectRoot = options.projectRoot ?? process.cwd();
|
|
182
470
|
const config = options.config ?? await loadConfig(projectRoot);
|
|
183
471
|
const scan = await scanContent(projectRoot, config);
|
|
184
472
|
const markdownFiles = filterPublished(scan.markdown, config);
|
|
185
473
|
const allSlugs = markdownFiles.map((file) => file.slug);
|
|
474
|
+
const wikilinkIndex = createWikiLinkResolutionIndex(
|
|
475
|
+
allSlugs,
|
|
476
|
+
config.ordering
|
|
477
|
+
);
|
|
186
478
|
const entries = [];
|
|
187
479
|
const graphLinks = {};
|
|
188
480
|
const brokenLinks = [];
|
|
189
481
|
const searchRecords = [];
|
|
190
|
-
const runtimeContentRoot =
|
|
482
|
+
const runtimeContentRoot = path4.join(projectRoot, ".silica/content");
|
|
191
483
|
const relativeGitPaths = markdownFiles.map(
|
|
192
|
-
(file) => normalizeGitPath(
|
|
484
|
+
(file) => normalizeGitPath(path4.join(config.contentDir, file.relativePath))
|
|
193
485
|
);
|
|
194
486
|
const gitDatesByPath = await getGitDatesForFiles(
|
|
195
487
|
projectRoot,
|
|
196
488
|
relativeGitPaths
|
|
197
489
|
);
|
|
198
|
-
await
|
|
199
|
-
await
|
|
490
|
+
await fs3.ensureDir(path4.join(projectRoot, ".silica"));
|
|
491
|
+
await fs3.ensureDir(path4.join(projectRoot, ".silica/next/public/silica"));
|
|
200
492
|
await writeRuntimeMarkdown(runtimeContentRoot, markdownFiles);
|
|
201
|
-
|
|
493
|
+
const analyses = await analyzeMarkdownFiles(markdownFiles, config, allSlugs, {
|
|
494
|
+
concurrency: options.analysisConcurrency,
|
|
495
|
+
wikilinkIndex
|
|
496
|
+
});
|
|
497
|
+
for (const [index, file] of markdownFiles.entries()) {
|
|
202
498
|
const gitDates = gitDatesByPath.get(
|
|
203
|
-
normalizeGitPath(
|
|
499
|
+
normalizeGitPath(path4.join(config.contentDir, file.relativePath))
|
|
204
500
|
) ?? {};
|
|
205
|
-
const analysis =
|
|
206
|
-
slug: asFullSlug(file.slug),
|
|
207
|
-
allSlugs,
|
|
208
|
-
assetBaseUrl: "/silica",
|
|
209
|
-
wikilinkStrategy: config.wikilinks.strategy,
|
|
210
|
-
tags: config.tags,
|
|
211
|
-
ordering: config.ordering
|
|
212
|
-
});
|
|
501
|
+
const analysis = analyses[index];
|
|
213
502
|
const title = analysis.title ?? titleFromFilePath(file.relativePath, config.ordering);
|
|
214
503
|
const menuLabel = getMenuLabel(file.frontmatter, title);
|
|
215
504
|
const sortKey = config.ordering.numericPrefixes && hasNumericPrefixInPath(file.relativePath) ? numericPrefixSortKey(file.relativePath) : void 0;
|
|
@@ -220,7 +509,7 @@ async function precompute(options = {}) {
|
|
|
220
509
|
description: analysis.description,
|
|
221
510
|
generatedDescription: analysis.generatedDescription,
|
|
222
511
|
tags: analysis.tags,
|
|
223
|
-
file: normalizeGitPath(
|
|
512
|
+
file: normalizeGitPath(path4.join(".silica/content", file.relativePath)),
|
|
224
513
|
relativeFile: file.relativePath,
|
|
225
514
|
sortKey,
|
|
226
515
|
created: stringifyDate(
|
|
@@ -229,12 +518,14 @@ async function precompute(options = {}) {
|
|
|
229
518
|
modified: stringifyDate(
|
|
230
519
|
getDate(file.frontmatter.modified) ?? gitDates.modified ?? file.stats.mtime
|
|
231
520
|
),
|
|
232
|
-
frontmatter: file.frontmatter
|
|
521
|
+
frontmatter: file.frontmatter,
|
|
522
|
+
contentHash: hashString(file.raw),
|
|
523
|
+
embeds: analysis.embeds
|
|
233
524
|
};
|
|
234
525
|
entries.push(entry);
|
|
235
526
|
graphLinks[file.slug] = analysis.links;
|
|
236
527
|
brokenLinks.push(...analysis.brokenLinks);
|
|
237
|
-
if (
|
|
528
|
+
if (isListedEntry2(entry)) {
|
|
238
529
|
searchRecords.push({
|
|
239
530
|
id: file.slug,
|
|
240
531
|
slug: file.slug,
|
|
@@ -248,27 +539,18 @@ async function precompute(options = {}) {
|
|
|
248
539
|
await copyAssets(projectRoot, config, scan.assets);
|
|
249
540
|
const manifest = makeManifest(config, entries);
|
|
250
541
|
const graph = makeGraph(graphLinks, brokenLinks);
|
|
251
|
-
const
|
|
252
|
-
await
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
makeNavigation(manifest)
|
|
264
|
-
);
|
|
265
|
-
await writeJson(path3.join(projectRoot, ".silica/graph.json"), graph);
|
|
266
|
-
await writeJson(path3.join(projectRoot, ".silica/config.json"), config);
|
|
267
|
-
await fs2.writeFile(
|
|
268
|
-
path3.join(projectRoot, ".silica/build-id.txt"),
|
|
269
|
-
`${buildId}
|
|
270
|
-
`
|
|
271
|
-
);
|
|
542
|
+
const renderHashes = makeRenderHashes(manifest, graph);
|
|
543
|
+
const cacheState = await makeRenderCacheState(projectRoot, config, manifest);
|
|
544
|
+
const prerender = makePrerenderManifest(manifest, graph, config);
|
|
545
|
+
await writeVaultDatabase(projectRoot, {
|
|
546
|
+
config,
|
|
547
|
+
manifest,
|
|
548
|
+
graph,
|
|
549
|
+
renderHashes,
|
|
550
|
+
cacheState,
|
|
551
|
+
prerender,
|
|
552
|
+
searchRecords
|
|
553
|
+
});
|
|
272
554
|
await writeSitemapAndRobots(projectRoot, config, manifest);
|
|
273
555
|
if (config.wikilinks.strict && brokenLinks.length > 0) {
|
|
274
556
|
const message = brokenLinks.map((link) => `${link.source} -> ${link.target}`).join("\n");
|
|
@@ -279,36 +561,304 @@ ${message}`);
|
|
|
279
561
|
manifest,
|
|
280
562
|
graph,
|
|
281
563
|
searchRecords,
|
|
282
|
-
|
|
564
|
+
prerender,
|
|
565
|
+
cacheState,
|
|
283
566
|
brokenLinks
|
|
284
567
|
};
|
|
285
568
|
}
|
|
569
|
+
async function analyzeMarkdownFiles(files, config, allSlugs, options) {
|
|
570
|
+
const workerCount = getAnalysisWorkerCount(files.length, options.concurrency);
|
|
571
|
+
if (workerCount <= 1) {
|
|
572
|
+
return analyzeMarkdownFilesSerial(files, config, options.wikilinkIndex);
|
|
573
|
+
}
|
|
574
|
+
return analyzeMarkdownFilesParallel(files, config, allSlugs, workerCount);
|
|
575
|
+
}
|
|
576
|
+
async function analyzeMarkdownFilesSerial(files, config, wikilinkIndex) {
|
|
577
|
+
const analyses = [];
|
|
578
|
+
for (const file of files) {
|
|
579
|
+
analyses.push(
|
|
580
|
+
await analyzeMarkdown(file.raw, {
|
|
581
|
+
slug: asFullSlug(file.slug),
|
|
582
|
+
wikilinkIndex,
|
|
583
|
+
assetBaseUrl: "/silica",
|
|
584
|
+
wikilinkStrategy: config.wikilinks.strategy,
|
|
585
|
+
tags: config.tags,
|
|
586
|
+
ordering: config.ordering
|
|
587
|
+
})
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
return analyses;
|
|
591
|
+
}
|
|
592
|
+
function analyzeMarkdownFilesParallel(files, config, allSlugs, workerCount) {
|
|
593
|
+
return new Promise((resolve, reject) => {
|
|
594
|
+
const workerUrl = new URL("./precompute-worker.js", import.meta.url);
|
|
595
|
+
const workers = [];
|
|
596
|
+
const analyses = new Array(files.length);
|
|
597
|
+
let nextIndex = 0;
|
|
598
|
+
let completed = 0;
|
|
599
|
+
let settled = false;
|
|
600
|
+
const rejectOnce = (error) => {
|
|
601
|
+
if (settled) return;
|
|
602
|
+
settled = true;
|
|
603
|
+
for (const worker of workers) {
|
|
604
|
+
void worker.terminate();
|
|
605
|
+
}
|
|
606
|
+
reject(error);
|
|
607
|
+
};
|
|
608
|
+
const resolveIfDone = () => {
|
|
609
|
+
if (completed < files.length || settled) return;
|
|
610
|
+
settled = true;
|
|
611
|
+
for (const worker of workers) {
|
|
612
|
+
void worker.terminate();
|
|
613
|
+
}
|
|
614
|
+
resolve(analyses);
|
|
615
|
+
};
|
|
616
|
+
const sendNext = (worker) => {
|
|
617
|
+
if (settled || nextIndex >= files.length) return;
|
|
618
|
+
const start = nextIndex;
|
|
619
|
+
const batch = files.slice(start, start + ANALYSIS_BATCH_SIZE).map((file, offset) => ({
|
|
620
|
+
index: start + offset,
|
|
621
|
+
slug: file.slug,
|
|
622
|
+
raw: file.raw
|
|
623
|
+
}));
|
|
624
|
+
nextIndex += batch.length;
|
|
625
|
+
worker.postMessage({
|
|
626
|
+
id: start,
|
|
627
|
+
files: batch
|
|
628
|
+
});
|
|
629
|
+
};
|
|
630
|
+
for (let index = 0; index < workerCount; index += 1) {
|
|
631
|
+
const worker = new Worker(workerUrl, {
|
|
632
|
+
workerData: {
|
|
633
|
+
allSlugs,
|
|
634
|
+
wikilinkStrategy: config.wikilinks.strategy,
|
|
635
|
+
tags: config.tags,
|
|
636
|
+
ordering: config.ordering
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
workers.push(worker);
|
|
640
|
+
worker.on("message", (message) => {
|
|
641
|
+
if (message.error) {
|
|
642
|
+
rejectOnce(new Error(message.error));
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
for (const result of message.results ?? []) {
|
|
646
|
+
analyses[result.index] = result.analysis;
|
|
647
|
+
}
|
|
648
|
+
completed += message.results?.length ?? 0;
|
|
649
|
+
resolveIfDone();
|
|
650
|
+
sendNext(worker);
|
|
651
|
+
});
|
|
652
|
+
worker.on("error", rejectOnce);
|
|
653
|
+
worker.on("exit", (code) => {
|
|
654
|
+
if (!settled && code !== 0) {
|
|
655
|
+
rejectOnce(
|
|
656
|
+
new Error(`Precompute analysis worker exited with ${code}`)
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
sendNext(worker);
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
function getAnalysisWorkerCount(fileCount, requestedConcurrency) {
|
|
665
|
+
if (fileCount === 0) return 1;
|
|
666
|
+
if (requestedConcurrency === void 0 && fileCount < MIN_PARALLEL_ANALYSIS_FILES) {
|
|
667
|
+
return 1;
|
|
668
|
+
}
|
|
669
|
+
const available = Math.max(
|
|
670
|
+
1,
|
|
671
|
+
os.availableParallelism?.() ?? os.cpus().length
|
|
672
|
+
);
|
|
673
|
+
const requested = getRequestedAnalysisConcurrency(
|
|
674
|
+
requestedConcurrency,
|
|
675
|
+
available
|
|
676
|
+
);
|
|
677
|
+
const usefulWorkers = Math.ceil(fileCount / ANALYSIS_BATCH_SIZE);
|
|
678
|
+
return Math.max(1, Math.min(fileCount, requested, usefulWorkers));
|
|
679
|
+
}
|
|
680
|
+
function getRequestedAnalysisConcurrency(requestedConcurrency, available) {
|
|
681
|
+
if (requestedConcurrency === void 0) {
|
|
682
|
+
return Math.min(available, MAX_ANALYSIS_WORKERS);
|
|
683
|
+
}
|
|
684
|
+
if (!Number.isFinite(requestedConcurrency)) return 1;
|
|
685
|
+
return Math.max(1, Math.floor(requestedConcurrency));
|
|
686
|
+
}
|
|
286
687
|
async function writeRuntimeMarkdown(runtimeContentRoot, files) {
|
|
287
|
-
await
|
|
688
|
+
await fs3.emptyDir(runtimeContentRoot);
|
|
288
689
|
for (const file of files) {
|
|
289
|
-
const destination =
|
|
290
|
-
await
|
|
291
|
-
await
|
|
690
|
+
const destination = path4.join(runtimeContentRoot, file.relativePath);
|
|
691
|
+
await fs3.ensureDir(path4.dirname(destination));
|
|
692
|
+
await fs3.writeFile(destination, file.raw);
|
|
292
693
|
}
|
|
293
694
|
}
|
|
294
|
-
function
|
|
695
|
+
async function makeRenderCacheState(projectRoot, config, manifest) {
|
|
696
|
+
const themeHash = await getThemeHash(projectRoot, config.theme);
|
|
697
|
+
const configHash = hashStable({
|
|
698
|
+
title: config.title,
|
|
699
|
+
description: config.description,
|
|
700
|
+
logo: config.logo,
|
|
701
|
+
baseUrl: config.baseUrl,
|
|
702
|
+
contentDir: config.contentDir,
|
|
703
|
+
theme: config.theme,
|
|
704
|
+
auth: config.auth,
|
|
705
|
+
wikilinks: config.wikilinks,
|
|
706
|
+
tags: config.tags,
|
|
707
|
+
ordering: config.ordering,
|
|
708
|
+
filters: config.filters
|
|
709
|
+
});
|
|
710
|
+
const navigationHash = hashStable({
|
|
711
|
+
entries: manifest.entries.filter(isListedEntry2).map((entry) => ({
|
|
712
|
+
slug: entry.slug,
|
|
713
|
+
menuLabel: entry.menuLabel,
|
|
714
|
+
sortKey: entry.sortKey
|
|
715
|
+
}))
|
|
716
|
+
});
|
|
717
|
+
const tagIndexHash = hashStable({
|
|
718
|
+
entries: manifest.entries.filter(isListedEntry2).map((entry) => ({
|
|
719
|
+
slug: entry.slug,
|
|
720
|
+
title: entry.title,
|
|
721
|
+
description: entry.description,
|
|
722
|
+
tags: entry.tags
|
|
723
|
+
}))
|
|
724
|
+
});
|
|
295
725
|
return {
|
|
296
|
-
version:
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
726
|
+
version: 1,
|
|
727
|
+
renderEnvironmentHash: hashStable({
|
|
728
|
+
version: RENDER_CACHE_SCHEMA_VERSION,
|
|
729
|
+
configHash,
|
|
730
|
+
themeHash
|
|
731
|
+
}),
|
|
732
|
+
configHash,
|
|
733
|
+
navigationHash,
|
|
734
|
+
tagIndexHash,
|
|
735
|
+
themeHash,
|
|
736
|
+
rendererVersion: RENDER_CACHE_SCHEMA_VERSION,
|
|
737
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
300
738
|
};
|
|
301
739
|
}
|
|
302
|
-
function
|
|
740
|
+
function makeRenderHashes(manifest, graph) {
|
|
741
|
+
const memo = /* @__PURE__ */ new Map();
|
|
742
|
+
const renderHashForSlug = (slug, seen = /* @__PURE__ */ new Set()) => {
|
|
743
|
+
const cached = memo.get(slug);
|
|
744
|
+
if (cached) return cached;
|
|
745
|
+
const entry = manifest.bySlug[slug];
|
|
746
|
+
if (!entry) return hashStable({ missing: slug });
|
|
747
|
+
if (seen.has(slug)) {
|
|
748
|
+
return hashStable({ slug, contentHash: entry.contentHash, cycle: true });
|
|
749
|
+
}
|
|
750
|
+
const nextSeen = new Set(seen).add(slug);
|
|
751
|
+
const embedded = entry.embeds.map((target) => ({
|
|
752
|
+
slug: target,
|
|
753
|
+
renderHash: renderHashForSlug(target, nextSeen)
|
|
754
|
+
}));
|
|
755
|
+
const backlinks = (graph.backlinks[slug] ?? []).map((source) => ({
|
|
756
|
+
slug: source,
|
|
757
|
+
title: manifest.bySlug[source]?.title ?? source
|
|
758
|
+
}));
|
|
759
|
+
const renderHash = hashStable({
|
|
760
|
+
entry: {
|
|
761
|
+
slug: entry.slug,
|
|
762
|
+
title: entry.title,
|
|
763
|
+
menuLabel: entry.menuLabel,
|
|
764
|
+
description: entry.description,
|
|
765
|
+
generatedDescription: entry.generatedDescription,
|
|
766
|
+
tags: entry.tags,
|
|
767
|
+
relativeFile: entry.relativeFile,
|
|
768
|
+
sortKey: entry.sortKey,
|
|
769
|
+
created: entry.created,
|
|
770
|
+
modified: entry.modified,
|
|
771
|
+
frontmatter: entry.frontmatter,
|
|
772
|
+
contentHash: entry.contentHash
|
|
773
|
+
},
|
|
774
|
+
embedded,
|
|
775
|
+
backlinks
|
|
776
|
+
});
|
|
777
|
+
memo.set(slug, renderHash);
|
|
778
|
+
return renderHash;
|
|
779
|
+
};
|
|
780
|
+
for (const slug of manifest.allSlugs) {
|
|
781
|
+
renderHashForSlug(slug);
|
|
782
|
+
}
|
|
783
|
+
return Object.fromEntries(memo);
|
|
784
|
+
}
|
|
785
|
+
function makePrerenderManifest(manifest, graph, config) {
|
|
303
786
|
return {
|
|
304
787
|
version: 1,
|
|
305
|
-
|
|
306
|
-
slug: entry.slug,
|
|
307
|
-
title: entry.menuLabel,
|
|
308
|
-
sortKey: entry.sortKey
|
|
309
|
-
}))
|
|
788
|
+
slugs: selectPrerenderSlugs(manifest, graph, config)
|
|
310
789
|
};
|
|
311
790
|
}
|
|
791
|
+
function selectPrerenderSlugs(manifest, graph, config) {
|
|
792
|
+
const prerender = config.render.prerender;
|
|
793
|
+
const entries = manifest.entries;
|
|
794
|
+
const scoreBySlug = /* @__PURE__ */ new Map();
|
|
795
|
+
let candidates = [];
|
|
796
|
+
if (prerender.strategy === "all") {
|
|
797
|
+
candidates = entries;
|
|
798
|
+
} else if (prerender.strategy === "depth") {
|
|
799
|
+
candidates = entries.filter(
|
|
800
|
+
(entry) => entry.slug === "index" || getSlugDepth(entry.slug) <= prerender.depth
|
|
801
|
+
);
|
|
802
|
+
} else if (prerender.strategy === "custom") {
|
|
803
|
+
const context = { manifest, graph };
|
|
804
|
+
candidates = entries.filter((entry) => {
|
|
805
|
+
const selected2 = prerender.select?.(entry, context);
|
|
806
|
+
if (typeof selected2 === "number" && Number.isFinite(selected2)) {
|
|
807
|
+
scoreBySlug.set(entry.slug, selected2);
|
|
808
|
+
return true;
|
|
809
|
+
}
|
|
810
|
+
return selected2 === true;
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
const selected = new Set(
|
|
814
|
+
[...candidates].sort((left, right) => {
|
|
815
|
+
const scoreDelta = (scoreBySlug.get(right.slug) ?? 0) - (scoreBySlug.get(left.slug) ?? 0);
|
|
816
|
+
return scoreDelta || compareManifestEntries(left, right);
|
|
817
|
+
}).slice(0, prerender.limit ?? candidates.length).map((entry) => entry.slug)
|
|
818
|
+
);
|
|
819
|
+
for (const slug of prerender.include ?? []) {
|
|
820
|
+
if (manifest.bySlug[slug]) selected.add(slug);
|
|
821
|
+
}
|
|
822
|
+
for (const slug of prerender.exclude ?? []) {
|
|
823
|
+
selected.delete(slug);
|
|
824
|
+
}
|
|
825
|
+
return manifest.entries.map((entry) => entry.slug).filter((slug) => selected.has(slug));
|
|
826
|
+
}
|
|
827
|
+
function getSlugDepth(slug) {
|
|
828
|
+
const segments = slug.split("/").filter(Boolean);
|
|
829
|
+
return Math.max(0, segments.length - 1);
|
|
830
|
+
}
|
|
831
|
+
async function getThemeHash(projectRoot, theme) {
|
|
832
|
+
const themeName = getThemeName(theme);
|
|
833
|
+
if (!themeName?.startsWith(".")) return void 0;
|
|
834
|
+
const themeRoot = path4.resolve(projectRoot, themeName);
|
|
835
|
+
if (!await fs3.pathExists(themeRoot)) return void 0;
|
|
836
|
+
const files = await readThemeFiles(themeRoot);
|
|
837
|
+
return hashStable(files);
|
|
838
|
+
}
|
|
839
|
+
function getThemeName(theme) {
|
|
840
|
+
if (typeof theme === "string") return theme;
|
|
841
|
+
if (typeof theme === "object" && theme !== null) return theme.name;
|
|
842
|
+
return void 0;
|
|
843
|
+
}
|
|
844
|
+
async function readThemeFiles(root, current = root) {
|
|
845
|
+
const entries = await fs3.readdir(current, { withFileTypes: true });
|
|
846
|
+
const results = [];
|
|
847
|
+
for (const entry of entries.sort(
|
|
848
|
+
(left, right) => left.name.localeCompare(right.name)
|
|
849
|
+
)) {
|
|
850
|
+
const absolutePath = path4.join(current, entry.name);
|
|
851
|
+
if (entry.isDirectory()) {
|
|
852
|
+
results.push(...await readThemeFiles(root, absolutePath));
|
|
853
|
+
} else if (entry.isFile()) {
|
|
854
|
+
results.push({
|
|
855
|
+
path: normalizeGitPath(path4.relative(root, absolutePath)),
|
|
856
|
+
content: await fs3.readFile(absolutePath, "utf8")
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return results;
|
|
861
|
+
}
|
|
312
862
|
async function getGitDates(projectRoot, relativePath) {
|
|
313
863
|
return (await getGitDatesForFiles(projectRoot, [normalizeGitPath(relativePath)])).get(normalizeGitPath(relativePath)) ?? {};
|
|
314
864
|
}
|
|
@@ -349,6 +899,26 @@ async function getGitDatesForFiles(projectRoot, relativePaths) {
|
|
|
349
899
|
function normalizeGitPath(relativePath) {
|
|
350
900
|
return relativePath.replace(/\\/g, "/");
|
|
351
901
|
}
|
|
902
|
+
function hashString(value) {
|
|
903
|
+
return crypto.createHash("sha256").update(value).digest("hex");
|
|
904
|
+
}
|
|
905
|
+
function hashStable(value) {
|
|
906
|
+
return hashString(stableStringify(value));
|
|
907
|
+
}
|
|
908
|
+
function stableStringify(value) {
|
|
909
|
+
if (value === void 0) return "undefined";
|
|
910
|
+
if (value instanceof Date) return JSON.stringify(value.toISOString());
|
|
911
|
+
if (Array.isArray(value)) {
|
|
912
|
+
return `[${value.map(stableStringify).join(",")}]`;
|
|
913
|
+
}
|
|
914
|
+
if (value && typeof value === "object") {
|
|
915
|
+
const entries = Object.entries(value).filter(([, entryValue]) => typeof entryValue !== "function").sort(([left], [right]) => left.localeCompare(right));
|
|
916
|
+
return `{${entries.map(
|
|
917
|
+
([key, entryValue]) => `${JSON.stringify(key)}:${stableStringify(entryValue)}`
|
|
918
|
+
).join(",")}}`;
|
|
919
|
+
}
|
|
920
|
+
return JSON.stringify(value);
|
|
921
|
+
}
|
|
352
922
|
function filterPublished(files, config) {
|
|
353
923
|
return files.filter((file) => {
|
|
354
924
|
if (config.filters.removeDrafts && file.frontmatter.draft === true)
|
|
@@ -400,33 +970,33 @@ function makeGraph(links, brokenLinks) {
|
|
|
400
970
|
};
|
|
401
971
|
}
|
|
402
972
|
async function copyAssets(projectRoot, config, assets) {
|
|
403
|
-
const destinationRoot =
|
|
404
|
-
await
|
|
973
|
+
const destinationRoot = path4.join(projectRoot, ".silica/next/public/silica");
|
|
974
|
+
await fs3.emptyDir(destinationRoot);
|
|
405
975
|
for (const asset of assets) {
|
|
406
|
-
await
|
|
407
|
-
|
|
976
|
+
await fs3.ensureDir(
|
|
977
|
+
path4.dirname(path4.join(destinationRoot, asset.relativePath))
|
|
408
978
|
);
|
|
409
|
-
await
|
|
979
|
+
await fs3.copyFile(
|
|
410
980
|
asset.absolutePath,
|
|
411
|
-
|
|
981
|
+
path4.join(destinationRoot, asset.relativePath)
|
|
412
982
|
);
|
|
413
983
|
}
|
|
414
|
-
await
|
|
415
|
-
await
|
|
984
|
+
await fs3.ensureDir(path4.join(projectRoot, ".silica/next/public"));
|
|
985
|
+
await fs3.writeFile(path4.join(destinationRoot, ".gitkeep"), "");
|
|
416
986
|
}
|
|
417
987
|
async function writeSitemapAndRobots(projectRoot, config, manifest) {
|
|
418
|
-
const publicRoot =
|
|
419
|
-
await
|
|
988
|
+
const publicRoot = path4.join(projectRoot, ".silica/next/public");
|
|
989
|
+
await fs3.ensureDir(publicRoot);
|
|
420
990
|
const baseUrl = (config.baseUrl ?? "http://localhost:3000").replace(
|
|
421
991
|
/\/$/,
|
|
422
992
|
""
|
|
423
993
|
);
|
|
424
|
-
const urls = manifest.entries.filter(
|
|
994
|
+
const urls = manifest.entries.filter(isListedEntry2).map(
|
|
425
995
|
(entry) => ` <url><loc>${baseUrl}${slugToHref(entry.slug)}</loc></url>`
|
|
426
996
|
).join("\n");
|
|
427
|
-
if (!await
|
|
428
|
-
await
|
|
429
|
-
|
|
997
|
+
if (!await fs3.pathExists(path4.join(projectRoot, "public/sitemap.xml"))) {
|
|
998
|
+
await fs3.writeFile(
|
|
999
|
+
path4.join(publicRoot, "sitemap.xml"),
|
|
430
1000
|
`<?xml version="1.0" encoding="UTF-8"?>
|
|
431
1001
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
432
1002
|
${urls}
|
|
@@ -434,9 +1004,9 @@ ${urls}
|
|
|
434
1004
|
`
|
|
435
1005
|
);
|
|
436
1006
|
}
|
|
437
|
-
if (!await
|
|
438
|
-
await
|
|
439
|
-
|
|
1007
|
+
if (!await fs3.pathExists(path4.join(projectRoot, "public/robots.txt"))) {
|
|
1008
|
+
await fs3.writeFile(
|
|
1009
|
+
path4.join(publicRoot, "robots.txt"),
|
|
440
1010
|
`User-agent: *
|
|
441
1011
|
Allow: /
|
|
442
1012
|
Sitemap: ${baseUrl}/sitemap.xml
|
|
@@ -444,10 +1014,6 @@ Sitemap: ${baseUrl}/sitemap.xml
|
|
|
444
1014
|
);
|
|
445
1015
|
}
|
|
446
1016
|
}
|
|
447
|
-
async function writeJson(filePath, value) {
|
|
448
|
-
await fs2.ensureDir(path3.dirname(filePath));
|
|
449
|
-
await fs2.writeJson(filePath, value, { spaces: 2 });
|
|
450
|
-
}
|
|
451
1017
|
function getDate(value) {
|
|
452
1018
|
if (value instanceof Date) return value;
|
|
453
1019
|
if (typeof value === "string" || typeof value === "number") {
|
|
@@ -456,14 +1022,14 @@ function getDate(value) {
|
|
|
456
1022
|
}
|
|
457
1023
|
return void 0;
|
|
458
1024
|
}
|
|
459
|
-
function
|
|
1025
|
+
function isListedEntry2(entry) {
|
|
460
1026
|
return entry.frontmatter.listed !== false;
|
|
461
1027
|
}
|
|
462
1028
|
function stringifyDate(value) {
|
|
463
1029
|
return value?.toISOString();
|
|
464
1030
|
}
|
|
465
1031
|
function titleFromFilePath(relativePath, ordering) {
|
|
466
|
-
const stem =
|
|
1032
|
+
const stem = path4.posix.basename(normalizeGitPath(relativePath)).replace(/\.(md|markdown|mdx)$/i, "");
|
|
467
1033
|
const title = ordering.numericPrefixes ? stripNumericPrefix(stem) : stem;
|
|
468
1034
|
return /^index$/i.test(title) ? "Home" : title;
|
|
469
1035
|
}
|
|
@@ -473,6 +1039,7 @@ export {
|
|
|
473
1039
|
asFullSlug,
|
|
474
1040
|
asRelativeURL,
|
|
475
1041
|
asSimpleSlug,
|
|
1042
|
+
createWikiLinkResolutionIndex,
|
|
476
1043
|
defineConfig,
|
|
477
1044
|
formatPropertyLabel,
|
|
478
1045
|
formatPropertyValue,
|