@riebeckite/core 0.0.15 → 0.0.17

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 CHANGED
@@ -32,7 +32,7 @@ import { defineConfig } from "@riebeckite/core";
32
32
 
33
33
  export default defineConfig({
34
34
  site: { title: "My notes", baseUrl: "https://example.com" },
35
- content: { directory: "../../content" },
35
+ content: { directory: "content" },
36
36
  });
37
37
  ```
38
38
 
package/README_ja.md CHANGED
@@ -25,7 +25,7 @@ import { defineConfig } from "@riebeckite/core";
25
25
 
26
26
  export default defineConfig({
27
27
  site: { title: "My notes", baseUrl: "https://example.com" },
28
- content: { directory: "../../content" },
28
+ content: { directory: "content" },
29
29
  });
30
30
  ```
31
31
 
package/dist/index.d.ts CHANGED
@@ -10,18 +10,21 @@ export { buildContentCollections } from "./src/content/content_collection.js";
10
10
  export { fingerprintContentEntries } from "./src/content/content_fingerprint.js";
11
11
  export type { ContentGraph, ContentGraphNeighbors, } from "./src/content/content_graph.js";
12
12
  export { createContentGraph } from "./src/content/content_graph.js";
13
+ export { getEntryLanguage, selectEntriesByLanguage, } from "./src/content/content_language.js";
13
14
  export { resolveDefaultContentLocation } from "./src/content/content_location.js";
14
15
  export type { ContentBuildOptions, ContentInspection, } from "./src/content/content_manager.js";
15
16
  export { ContentManager } from "./src/content/content_manager.js";
16
17
  export { extractFrontmatterAliases } from "./src/content/content_metadata.js";
18
+ export { computePipelineFingerprint, isPersistentlyCacheable, } from "./src/content/content_persistent_cache.js";
17
19
  export type { ContentQueryDateFilter, ContentQueryDateGranularity, ContentQueryFilter, ContentQueryFrontmatterFilter, ContentQueryGroup, ContentQueryGroupBy, ContentQueryGroupOptions, ContentQueryPage, ContentQueryPagination, ContentQueryScalar, ContentQuerySort, ContentQuerySortOrder, ContentQuerySpec, ContentQueryTagFilter, } from "./src/content/content_query.js";
18
20
  export { groupContentEntries, queryContentEntries, queryContentPage, resolveContentQueryPagination, } from "./src/content/content_query.js";
19
21
  export type { ContentSource, ContentSourceContent, ContentSourceEntry, ContentSourceMetadata, } from "./src/content/content_source.js";
20
22
  export { getContentSourceEntry, readContentSourceEntry, } from "./src/content/content_source.js";
21
23
  export { resolveContentStableId } from "./src/content/content_stable_id.js";
22
- export { FileSystemContentSource, INTERNAL_CONTENT_IGNORE_PATTERNS, isIgnoredContentPath, } from "./src/content/file_system_content_source.js";
24
+ export type { ContentSourceExclusion, ContentSourceScan, } from "./src/content/file_system_content_source.js";
25
+ export { FileSystemContentSource, INTERNAL_CONTENT_IGNORE_PATTERNS, isIgnoredContentPath, matchContentExcludePattern, } from "./src/content/file_system_content_source.js";
23
26
  export type { FolderEntryCandidate, FolderEntryKind, FolderEntryResolution, FolderEntryScope, FolderLocationResolution, ResolveFolderEntryOptions, } from "./src/content/folder_entry.js";
24
- export { resolveFolderEntry, resolveFolderLocation, resolveGeneratedFolderLocation, } from "./src/content/folder_entry.js";
27
+ export { resolveFolderEntry, resolveFolderLocation, resolveGeneratedFolderLocation, resolvePublicFolderLocation, } from "./src/content/folder_entry.js";
25
28
  export type { ForceGraphLayoutOptions, GraphEdge, GraphLayoutNode, LinkableGraphNode, RadialGraphLayoutOptions, } from "./src/content/graph_layout.js";
26
29
  export { buildGraphEdges, layoutForceGraph, layoutRadialGraph, } from "./src/content/graph_layout.js";
27
30
  export { IMAGE_EXTENSIONS } from "./src/content/image_extensions.js";
package/dist/index.js CHANGED
@@ -19,6 +19,7 @@ function validateConfigIssues(config) {
19
19
  }
20
20
  validateSite(value.site, issues);
21
21
  validateContent(value.content, issues);
22
+ validateCache(value.cache, issues);
22
23
  validateTheme(value.theme, issues);
23
24
  validatePlugins(value.plugins, issues);
24
25
  return issues;
@@ -93,6 +94,15 @@ function validatePublishStrategy(value, issues) {
93
94
  });
94
95
  }
95
96
  }
97
+ function validateCache(value, issues) {
98
+ if (value === void 0) return;
99
+ if (!isRecord(value)) {
100
+ issues.push({ path: "cache", message: "Expected an object." });
101
+ return;
102
+ }
103
+ validateOptionalBoolean(value.enabled, "cache.enabled", issues);
104
+ validateOptionalString(value.directory, "cache.directory", issues);
105
+ }
96
106
  function validateTheme(value, issues) {
97
107
  if (value === void 0) return;
98
108
  if (!isRecord(value)) {
@@ -626,18 +636,13 @@ function resolveConfig(config) {
626
636
  }
627
637
  },
628
638
  content: {
629
- directory: config.content?.directory ?? "../../content",
639
+ directory: config.content?.directory ?? "content",
630
640
  source: config.content?.source,
631
641
  exclude: config.content?.exclude ?? [],
632
642
  filters: {
633
643
  publishStrategy: config.content?.filters?.publishStrategy ?? "explicit"
634
644
  }
635
645
  },
636
- markdown: {
637
- syntaxHighlight: {
638
- theme: config.markdown?.syntaxHighlight?.theme ?? ""
639
- }
640
- },
641
646
  theme,
642
647
  plugins: resolvePlugins(config.plugins),
643
648
  cache: {
@@ -805,7 +810,7 @@ import fs from "node:fs/promises";
805
810
  import path from "node:path";
806
811
 
807
812
  // src/content/content_build_state.ts
808
- var CONTENT_BUILD_STATE_VERSION = 6;
813
+ var CONTENT_BUILD_STATE_VERSION = 7;
809
814
  var CONTENT_BUILD_STATE_DIRECTORY = ".riebeckite";
810
815
  var CONTENT_BUILD_STATE_EXCLUDE = `${CONTENT_BUILD_STATE_DIRECTORY}/**`;
811
816
 
@@ -1322,6 +1327,23 @@ function isResolvedNoteLink(link) {
1322
1327
  return link.kind === "note" && link.slug !== null;
1323
1328
  }
1324
1329
 
1330
+ // src/content/content_language.ts
1331
+ function getEntryLanguage(entry) {
1332
+ return entry.publicLocation.language;
1333
+ }
1334
+ function selectEntriesByLanguage(entries, language) {
1335
+ if (!language) {
1336
+ return entries;
1337
+ }
1338
+ const localized = entries.filter(
1339
+ (entry) => getEntryLanguage(entry) !== void 0
1340
+ );
1341
+ if (localized.length === 0) {
1342
+ return entries;
1343
+ }
1344
+ return localized.filter((entry) => getEntryLanguage(entry) === language);
1345
+ }
1346
+
1325
1347
  // src/content/content_location.ts
1326
1348
  function resolveDefaultContentLocation(content) {
1327
1349
  return {
@@ -1479,6 +1501,8 @@ import { matter as matter2 } from "vfile-matter";
1479
1501
 
1480
1502
  // src/content/content_dependency_tracker.ts
1481
1503
  import { createHash as createHash2 } from "node:crypto";
1504
+ var LINK_INDEX_PREFIX = "index:";
1505
+ var LINK_LOCATION_PREFIX = "location:";
1482
1506
  function createContentDependencyTracker(source) {
1483
1507
  const dependencies = /* @__PURE__ */ new Map();
1484
1508
  const record = (identity, content) => {
@@ -1524,6 +1548,30 @@ function dependencyKey(dependency) {
1524
1548
  return `${normalized.kind}:${normalized.id}`;
1525
1549
  }
1526
1550
 
1551
+ // src/content/content_links.ts
1552
+ var WIKILINK_PATTERN = /(!)?\[\[([^\]|#^]+)(?:[#^][^\]|]+)?(?:\|[^\]]+)?\]\]/g;
1553
+ function extractContentLinks(markdown, contentIndex) {
1554
+ WIKILINK_PATTERN.lastIndex = 0;
1555
+ const links = [];
1556
+ for (let match = WIKILINK_PATTERN.exec(markdown); match !== null; match = WIKILINK_PATTERN.exec(markdown)) {
1557
+ const rawTarget = match[2]?.trim();
1558
+ if (!rawTarget) continue;
1559
+ const resolved = contentIndex.get(rawTarget.toLowerCase()) ?? null;
1560
+ links.push({
1561
+ raw: rawTarget,
1562
+ slug: resolved,
1563
+ kind: resolved ? linkKind(resolved) : "unresolved",
1564
+ embed: match[1] === "!"
1565
+ });
1566
+ }
1567
+ return links;
1568
+ }
1569
+ function linkKind(value) {
1570
+ if (isImagePath(value)) return "image";
1571
+ if (isAttachmentPath(value)) return "attachment";
1572
+ return "note";
1573
+ }
1574
+
1527
1575
  // src/content/content_persistent_cache.ts
1528
1576
  import { createHash as createHash4 } from "node:crypto";
1529
1577
  import fs2 from "node:fs/promises";
@@ -1720,7 +1768,6 @@ function computePipelineFingerprint(config) {
1720
1768
  const fingerprintData = {
1721
1769
  compatibilityVersion: CONTENT_CACHE_COMPATIBILITY_VERSION,
1722
1770
  plugins: plugins.map((p) => pluginFingerprint(p)),
1723
- markdown: config.markdown,
1724
1771
  content: {
1725
1772
  filters: config.content.filters
1726
1773
  }
@@ -1885,6 +1932,7 @@ async function writeAtomically2(targetPath, versionDirectory2, serialized) {
1885
1932
  }
1886
1933
  function resolvePluginCacheDirectory(config) {
1887
1934
  if (!config) return path3.resolve(process.cwd(), ".riebeckite", "cache");
1935
+ if (config.cache?.directory) return config.cache.directory;
1888
1936
  if (config.buildDirectory) return path3.join(config.buildDirectory, "cache");
1889
1937
  return path3.resolve(config.content.directory, "..", ".riebeckite", "cache");
1890
1938
  }
@@ -2153,6 +2201,9 @@ var Pipeline = class {
2153
2201
  frontmatter: file.data.matter || {},
2154
2202
  html: String(file.value)
2155
2203
  };
2204
+ if (embedDepth === 0 && sourceSlug && dependencyTracker) {
2205
+ this.recordNoteLinkDependencies(markDownContent, dependencyTracker);
2206
+ }
2156
2207
  if (sourceSlug && dependencyTracker) {
2157
2208
  this.reportContentDependencies(
2158
2209
  sourceSlug,
@@ -2202,6 +2253,25 @@ var Pipeline = class {
2202
2253
  this.options.isRoutable
2203
2254
  );
2204
2255
  }
2256
+ recordNoteLinkDependencies(markdown, dependencyTracker) {
2257
+ for (const link of extractContentLinks(markdown, this.contentIndex)) {
2258
+ if (link.slug !== null && link.kind === "note") {
2259
+ const id = `${LINK_LOCATION_PREFIX}${link.slug}`;
2260
+ dependencyTracker.recordLinkResolution(
2261
+ id,
2262
+ this.resolveLinkDependency(id)
2263
+ );
2264
+ continue;
2265
+ }
2266
+ if (link.kind === "unresolved") {
2267
+ const id = `${LINK_INDEX_PREFIX}${link.raw.toLowerCase()}`;
2268
+ dependencyTracker.recordLinkResolution(
2269
+ id,
2270
+ this.resolveLinkDependency(id)
2271
+ );
2272
+ }
2273
+ }
2274
+ }
2205
2275
  use(processor, plugin, options) {
2206
2276
  const register = processor.use.bind(processor);
2207
2277
  if (options === void 0) {
@@ -2305,8 +2375,6 @@ function normalizeMarkdownLinks(context) {
2305
2375
  });
2306
2376
  };
2307
2377
  }
2308
- var LINK_INDEX_PREFIX = "index:";
2309
- var LINK_LOCATION_PREFIX = "location:";
2310
2378
  function resolveLinkDependencyValue(id, contentIndex, lookupPermalink, isRoutable) {
2311
2379
  if (id.startsWith(LINK_INDEX_PREFIX)) {
2312
2380
  return contentIndex.get(id.slice(LINK_INDEX_PREFIX.length)) ?? "";
@@ -2517,12 +2585,26 @@ var GeneratedOutputRegistry = class {
2517
2585
  };
2518
2586
 
2519
2587
  // src/plugin/plugin_lifecycle.ts
2520
- var PluginLifecycleError = class extends Error {
2588
+ var PluginHookError = class extends Error {
2521
2589
  constructor(pluginName, hookName, cause) {
2522
2590
  super(`Plugin "${pluginName}" failed during "${hookName}"`, { cause });
2523
- this.name = "PluginLifecycleError";
2591
+ this.name = "PluginHookError";
2524
2592
  }
2525
2593
  };
2594
+ async function runPluginHook(plugin, hookName, context, hook) {
2595
+ try {
2596
+ return await context.tracer.span(
2597
+ `plugin.${hookName}`,
2598
+ {
2599
+ plugin: plugin.name,
2600
+ hook: hookName
2601
+ },
2602
+ () => hook(context)
2603
+ );
2604
+ } catch (error) {
2605
+ throw new PluginHookError(plugin.name, hookName, error);
2606
+ }
2607
+ }
2526
2608
  async function runSetup(plugins, createContext) {
2527
2609
  await runLifecycleHook(
2528
2610
  plugins,
@@ -2559,19 +2641,7 @@ async function runLifecycleHook(plugins, hookName, getHook, createContext) {
2559
2641
  for (const plugin of plugins) {
2560
2642
  const hook = getHook(plugin);
2561
2643
  if (!hook) continue;
2562
- try {
2563
- const context = createContext(plugin);
2564
- await context.tracer.span(
2565
- `plugin.${hookName}`,
2566
- {
2567
- plugin: plugin.name,
2568
- hook: hookName
2569
- },
2570
- () => hook(context)
2571
- );
2572
- } catch (error) {
2573
- throw new PluginLifecycleError(plugin.name, hookName, error);
2574
- }
2644
+ await runPluginHook(plugin, hookName, createContext(plugin), hook);
2575
2645
  }
2576
2646
  }
2577
2647
 
@@ -2589,6 +2659,7 @@ var PluginRuntime = class {
2589
2659
  generatedOutputs = new GeneratedOutputRegistry();
2590
2660
  isBuildTime = false;
2591
2661
  resolvedPlugins = null;
2662
+ pageDeclarations = /* @__PURE__ */ new WeakMap();
2592
2663
  getDiagnostics() {
2593
2664
  return this.diagnostics;
2594
2665
  }
@@ -2599,7 +2670,7 @@ var PluginRuntime = class {
2599
2670
  * Starts the plugin build lifecycle exactly once, regardless of which public
2600
2671
  * entry point (content processing or location resolution) reaches it first.
2601
2672
  * Concurrent callers await the same run, so the ordered hooks
2602
- * (`setup` → `buildStart` → `onBuildStart` → `onConfigResolved`) are never
2673
+ * (`setup` → `buildStart` → `onConfigResolved`) are never
2603
2674
  * duplicated and no caller proceeds before the lifecycle completes.
2604
2675
  */
2605
2676
  startBuild(contentIndex) {
@@ -2607,7 +2678,7 @@ var PluginRuntime = class {
2607
2678
  return this.buildLifecycle;
2608
2679
  }
2609
2680
  async runContentLoaded(slug, markdown, contentIndex) {
2610
- await this.runHook((plugin) => plugin.onContentLoaded, {
2681
+ await this.runHook("onContentLoaded", (plugin) => plugin.onContentLoaded, {
2611
2682
  ...this.createContext(contentIndex),
2612
2683
  slug,
2613
2684
  markdown
@@ -2619,22 +2690,30 @@ var PluginRuntime = class {
2619
2690
  for (const plugin of this.plugins()) {
2620
2691
  const resolver = plugin.resolveContentLocations;
2621
2692
  if (!resolver) continue;
2622
- const resolved = await resolver(
2623
- this.createPluginContext(plugin, context)
2693
+ const pluginContext = this.createPluginContext(plugin, context);
2694
+ const resolved = await runPluginHook(
2695
+ plugin,
2696
+ "resolveContentLocations",
2697
+ pluginContext,
2698
+ resolver
2624
2699
  );
2625
2700
  locations.push(...resolved);
2626
2701
  }
2627
2702
  return locations;
2628
2703
  }
2629
2704
  async extendContentLocations(entries, locations, contentIndex) {
2630
- await this.runHook((plugin) => plugin.extendContentLocations, {
2631
- ...this.createContext(contentIndex),
2632
- entries,
2633
- locations
2634
- });
2705
+ await this.runHook(
2706
+ "extendContentLocations",
2707
+ (plugin) => plugin.extendContentLocations,
2708
+ {
2709
+ ...this.createContext(contentIndex),
2710
+ entries,
2711
+ locations
2712
+ }
2713
+ );
2635
2714
  }
2636
2715
  async runPostHook(hookName, slug, markdown, content, contentIndex) {
2637
- await this.runHook((plugin) => plugin[hookName], {
2716
+ await this.runHook(hookName, (plugin) => plugin[hookName], {
2638
2717
  ...this.createContext(contentIndex),
2639
2718
  slug,
2640
2719
  markdown,
@@ -2642,16 +2721,24 @@ var PluginRuntime = class {
2642
2721
  });
2643
2722
  }
2644
2723
  async runGraphHook(entries, contentIndex) {
2645
- await this.runHook((plugin) => plugin.extendContentGraph, {
2646
- ...this.createContext(contentIndex),
2647
- entries
2648
- });
2724
+ await this.runHook(
2725
+ "extendContentGraph",
2726
+ (plugin) => plugin.extendContentGraph,
2727
+ {
2728
+ ...this.createContext(contentIndex),
2729
+ entries
2730
+ }
2731
+ );
2649
2732
  }
2650
2733
  async runManifestCreated(manifest, contentIndex) {
2651
- await this.runHook((plugin) => plugin.onManifestCreated, {
2652
- ...this.createContext(contentIndex),
2653
- manifest
2654
- });
2734
+ await this.runHook(
2735
+ "onManifestCreated",
2736
+ (plugin) => plugin.onManifestCreated,
2737
+ {
2738
+ ...this.createContext(contentIndex),
2739
+ manifest
2740
+ }
2741
+ );
2655
2742
  }
2656
2743
  async runBuildEnd(manifest, contentIndex) {
2657
2744
  const context = {
@@ -2662,10 +2749,6 @@ var PluginRuntime = class {
2662
2749
  this.plugins(),
2663
2750
  (plugin) => this.createPluginContext(plugin, context)
2664
2751
  );
2665
- await this.runHook((plugin) => plugin.onBuildEnd, {
2666
- ...this.createContext(contentIndex),
2667
- manifest
2668
- });
2669
2752
  }
2670
2753
  enableBuildTime() {
2671
2754
  this.isBuildTime = true;
@@ -2747,63 +2830,74 @@ var PluginRuntime = class {
2747
2830
  return highest[0].page;
2748
2831
  }
2749
2832
  async getPagePaths(manifest, contentIndex) {
2750
- const paths = [];
2751
- const context = { ...this.createContext(contentIndex), manifest };
2752
- for (const plugin of this.plugins()) {
2753
- for (const pageType of plugin.pageTypes ?? []) {
2754
- const declared = pageType.paths;
2755
- if (!declared) continue;
2756
- const resolved = typeof declared === "function" ? await declared(this.createPluginContext(plugin, context)) : declared;
2757
- for (const rawPath of resolved) {
2758
- paths.push(normalizePageTypePath(rawPath, pageType));
2759
- }
2760
- }
2761
- }
2762
- return [...new Set(paths)];
2833
+ return [
2834
+ ...new Set(
2835
+ (await this.getPageDeclarations(manifest, contentIndex)).map(
2836
+ (declaration) => declaration.pathname
2837
+ )
2838
+ )
2839
+ ];
2763
2840
  }
2764
2841
  async getPageRoutes(manifest, contentIndex) {
2765
- const routes = [];
2766
- const context = { ...this.createContext(contentIndex), manifest };
2767
- for (const plugin of this.plugins()) {
2768
- for (const pageType of plugin.pageTypes ?? []) {
2769
- const declared = pageType.paths;
2770
- if (!declared) continue;
2771
- const paths = typeof declared === "function" ? await declared(this.createPluginContext(plugin, context)) : declared;
2772
- for (const pathname of paths) {
2773
- routes.push({
2774
- pathname: normalizePageTypePath(pathname, pageType),
2775
- pluginName: plugin.name,
2776
- pageType: pageType.id
2777
- });
2778
- }
2779
- }
2780
- }
2781
- return routes;
2842
+ return (await this.getPageDeclarations(manifest, contentIndex)).map(
2843
+ ({ pathname, plugin, pageType }) => ({
2844
+ pathname,
2845
+ pluginName: plugin.name,
2846
+ pageType: pageType.id
2847
+ })
2848
+ );
2782
2849
  }
2783
2850
  async getPageOutputs(manifest, contentIndex) {
2784
- const outputs = [];
2851
+ const outputs = await Promise.all(
2852
+ (await this.getPageDeclarations(manifest, contentIndex)).map(
2853
+ async ({ pathname, plugin, pageType }) => {
2854
+ const context = {
2855
+ ...this.createContext(contentIndex),
2856
+ manifest,
2857
+ pathname
2858
+ };
2859
+ const dependencies = pageType.outputDependencies ? typeof pageType.outputDependencies === "function" ? await pageType.outputDependencies(
2860
+ this.createPluginContext(plugin, context)
2861
+ ) : pageType.outputDependencies : [{ type: "unknown" }];
2862
+ return {
2863
+ output: {
2864
+ kind: "plugin-page",
2865
+ path: pathname,
2866
+ producer: `plugin:${plugin.name}:page:${pageType.id}`,
2867
+ dependencies
2868
+ },
2869
+ priority: pageType.priority ?? 0
2870
+ };
2871
+ }
2872
+ )
2873
+ );
2874
+ return uniqueOutputs(outputs);
2875
+ }
2876
+ async getPageDeclarations(manifest, contentIndex) {
2877
+ const existing = this.pageDeclarations.get(manifest);
2878
+ if (existing) return await existing;
2879
+ const declarations = this.resolvePageDeclarations(manifest, contentIndex);
2880
+ this.pageDeclarations.set(manifest, declarations);
2881
+ return await declarations;
2882
+ }
2883
+ async resolvePageDeclarations(manifest, contentIndex) {
2884
+ const declarations = [];
2785
2885
  const context = { ...this.createContext(contentIndex), manifest };
2786
2886
  for (const plugin of this.plugins()) {
2787
2887
  for (const pageType of plugin.pageTypes ?? []) {
2788
- const declared = pageType.paths;
2789
- if (!declared) continue;
2790
- const paths = typeof declared === "function" ? await declared(this.createPluginContext(plugin, context)) : declared;
2791
- for (const rawPath of paths) {
2792
- const pathname = normalizePageTypePath(rawPath, pageType);
2793
- const dependencyContext = { ...context, pathname };
2794
- const dependencies = pageType.outputDependencies ? typeof pageType.outputDependencies === "function" ? await pageType.outputDependencies(
2795
- this.createPluginContext(plugin, dependencyContext)
2796
- ) : pageType.outputDependencies : [{ type: "unknown" }];
2797
- outputs.push({
2798
- kind: "plugin-page",
2799
- path: pathname,
2800
- producer: `plugin:${plugin.name}:page:${pageType.id}`,
2801
- dependencies
2888
+ const paths = pageType.paths;
2889
+ if (!paths) continue;
2890
+ const resolved = typeof paths === "function" ? await paths(this.createPluginContext(plugin, context)) : paths;
2891
+ for (const pathname of resolved) {
2892
+ declarations.push({
2893
+ pathname: normalizePageTypePath(pathname, pageType),
2894
+ plugin,
2895
+ pageType
2802
2896
  });
2803
2897
  }
2804
2898
  }
2805
2899
  }
2806
- return uniqueOutputs(outputs);
2900
+ return declarations;
2807
2901
  }
2808
2902
  getContentOutputDependencies() {
2809
2903
  return this.plugins().flatMap((plugin) => plugin.outputDependencies ?? []);
@@ -2814,9 +2908,13 @@ var PluginRuntime = class {
2814
2908
  const results = [];
2815
2909
  const context = this.createContext(contentIndex);
2816
2910
  for (const plugin of this.plugins()) {
2817
- const diagnostics = await plugin.addDiagnostics?.(
2818
- this.createPluginContext(plugin, context)
2819
- );
2911
+ const hook = plugin.addDiagnostics;
2912
+ const diagnostics = hook ? await runPluginHook(
2913
+ plugin,
2914
+ "addDiagnostics",
2915
+ this.createPluginContext(plugin, context),
2916
+ hook
2917
+ ) : void 0;
2820
2918
  for (const diagnostic of diagnostics ?? []) {
2821
2919
  results.push({
2822
2920
  ...diagnostic,
@@ -2845,13 +2943,13 @@ var PluginRuntime = class {
2845
2943
  stage: "buildStart",
2846
2944
  run: () => runBuildStart(this.plugins(), createPluginContext)
2847
2945
  },
2848
- {
2849
- stage: "onBuildStart",
2850
- run: () => this.runHook((plugin) => plugin.onBuildStart, context)
2851
- },
2852
2946
  {
2853
2947
  stage: "onConfigResolved",
2854
- run: () => this.runHook((plugin) => plugin.onConfigResolved, context)
2948
+ run: () => this.runHook(
2949
+ "onConfigResolved",
2950
+ (plugin) => plugin.onConfigResolved,
2951
+ context
2952
+ )
2855
2953
  }
2856
2954
  ];
2857
2955
  for (const { stage, run } of stages) {
@@ -2896,18 +2994,12 @@ var PluginRuntime = class {
2896
2994
  this.pluginCaches.set(plugin.name, cache);
2897
2995
  return cache;
2898
2996
  }
2899
- async runHook(hook, context) {
2997
+ async runHook(hookName, hook, context) {
2900
2998
  for (const plugin of this.plugins()) {
2901
2999
  const pluginHook = hook(plugin);
2902
3000
  if (!pluginHook) continue;
2903
3001
  const pluginContext = this.createPluginContext(plugin, context);
2904
- await pluginContext.tracer.span(
2905
- "plugin.hook",
2906
- {
2907
- plugin: plugin.name
2908
- },
2909
- () => pluginHook(pluginContext)
2910
- );
3002
+ await runPluginHook(plugin, hookName, pluginContext, pluginHook);
2911
3003
  }
2912
3004
  }
2913
3005
  observability() {
@@ -2993,10 +3085,20 @@ function countSeverity(diagnostics, severity) {
2993
3085
  }
2994
3086
  function uniqueOutputs(outputs) {
2995
3087
  const byPath = /* @__PURE__ */ new Map();
2996
- for (const output of outputs) byPath.set(output.path, output);
2997
- return [...byPath.values()].sort(
2998
- (left, right) => left.path.localeCompare(right.path)
2999
- );
3088
+ for (const output of outputs) {
3089
+ const previous = byPath.get(output.output.path);
3090
+ if (!previous || output.priority > previous.priority) {
3091
+ byPath.set(output.output.path, output);
3092
+ continue;
3093
+ }
3094
+ if (output.output.producer === previous.output.producer) continue;
3095
+ if (output.priority === previous.priority) {
3096
+ throw new Error(
3097
+ `Multiple plugin page types declare ${output.output.path} at priority ${output.priority}: ${previous.output.producer}, ${output.output.producer}`
3098
+ );
3099
+ }
3100
+ }
3101
+ return [...byPath.values()].map(({ output }) => output).sort((left, right) => left.path.localeCompare(right.path));
3000
3102
  }
3001
3103
 
3002
3104
  // src/content/affected_content.ts
@@ -3411,8 +3513,8 @@ function collectDependencies2(manifestEntry, trackedDependencies) {
3411
3513
  kind: "file",
3412
3514
  id: asset.path.replace(/\\/g, "/")
3413
3515
  })),
3414
- ...trackedDependencies.filter(
3415
- (dependency) => dependency.kind === "content" || dependency.kind === "file"
3516
+ ...trackedDependencies.map(toBuildDependency).filter(
3517
+ (dependency) => dependency !== null
3416
3518
  ).map((dependency) => ({
3417
3519
  kind: dependency.kind,
3418
3520
  id: dependency.id.replace(/\\/g, "/")
@@ -3426,6 +3528,18 @@ function collectDependencies2(manifestEntry, trackedDependencies) {
3426
3528
  (left, right) => `${left.kind}:${left.id}`.localeCompare(`${right.kind}:${right.id}`)
3427
3529
  );
3428
3530
  }
3531
+ function toBuildDependency(dependency) {
3532
+ if (dependency.kind === "content" || dependency.kind === "file") {
3533
+ return { kind: dependency.kind, id: dependency.id };
3534
+ }
3535
+ if (dependency.id.startsWith(LINK_LOCATION_PREFIX)) {
3536
+ return {
3537
+ kind: "content",
3538
+ id: dependency.id.slice(LINK_LOCATION_PREFIX.length)
3539
+ };
3540
+ }
3541
+ return null;
3542
+ }
3429
3543
  function collectLinkTargets(manifestEntry) {
3430
3544
  if (!manifestEntry) return [];
3431
3545
  return [
@@ -3615,8 +3729,17 @@ var FileSystemContentSource = class {
3615
3729
  contentDirectory;
3616
3730
  exclude;
3617
3731
  async scan() {
3732
+ return (await this.scanWithExclusions()).entries;
3733
+ }
3734
+ async scanWithExclusions() {
3735
+ const exclusions = [];
3618
3736
  try {
3619
- return await this.scanDirectory(this.contentDirectory, "");
3737
+ const entries = await this.scanDirectory(
3738
+ this.contentDirectory,
3739
+ "",
3740
+ exclusions
3741
+ );
3742
+ return { entries, exclusions };
3620
3743
  } catch (error) {
3621
3744
  if (isMissingDirectoryError(error, this.contentDirectory)) {
3622
3745
  throw missingContentDirectoryError(this.contentDirectory);
@@ -3632,7 +3755,7 @@ var FileSystemContentSource = class {
3632
3755
  }
3633
3756
  return await fs4.readFile(filePath);
3634
3757
  }
3635
- async scanDirectory(directory, parentPath2) {
3758
+ async scanDirectory(directory, parentPath2, exclusions) {
3636
3759
  const directoryEntries = await fs4.readdir(directory, {
3637
3760
  withFileTypes: true
3638
3761
  });
@@ -3643,9 +3766,16 @@ var FileSystemContentSource = class {
3643
3766
  parentPath2 ? `${parentPath2}/${directoryEntry.name}` : directoryEntry.name
3644
3767
  );
3645
3768
  const filePath = path4.join(directory, directoryEntry.name);
3646
- if (this.isExcluded(logicalPath)) continue;
3769
+ if (isExcluded(INTERNAL_CONTENT_IGNORE_PATTERNS, logicalPath)) continue;
3770
+ const pattern = matchContentExcludePattern(logicalPath, this.exclude);
3771
+ if (pattern !== void 0) {
3772
+ exclusions.push({ path: logicalPath, pattern });
3773
+ continue;
3774
+ }
3647
3775
  if (directoryEntry.isDirectory()) {
3648
- entries.push(...await this.scanDirectory(filePath, logicalPath));
3776
+ entries.push(
3777
+ ...await this.scanDirectory(filePath, logicalPath, exclusions)
3778
+ );
3649
3779
  continue;
3650
3780
  }
3651
3781
  if (!directoryEntry.isFile()) continue;
@@ -3660,9 +3790,6 @@ var FileSystemContentSource = class {
3660
3790
  }
3661
3791
  return entries;
3662
3792
  }
3663
- isExcluded(logicalPath) {
3664
- return isIgnoredContentPath(logicalPath, this.exclude);
3665
- }
3666
3793
  resolveFilePath(logicalPath) {
3667
3794
  const normalizedPath = normalizeLogicalPath(logicalPath);
3668
3795
  const filePath = path4.resolve(this.contentDirectory, normalizedPath);
@@ -3675,8 +3802,14 @@ var FileSystemContentSource = class {
3675
3802
  };
3676
3803
  function isIgnoredContentPath(logicalPath, exclude = []) {
3677
3804
  if (isExcluded(INTERNAL_CONTENT_IGNORE_PATTERNS, logicalPath)) return true;
3678
- if (isExcluded(exclude, logicalPath)) return true;
3679
- return logicalPath.endsWith(".md") && isExcluded(exclude, logicalPath.slice(0, -3));
3805
+ return matchContentExcludePattern(logicalPath, exclude) !== void 0;
3806
+ }
3807
+ function matchContentExcludePattern(logicalPath, exclude = []) {
3808
+ const direct = exclude.find((pattern) => isExcluded([pattern], logicalPath));
3809
+ if (direct !== void 0) return direct;
3810
+ if (!logicalPath.endsWith(".md")) return void 0;
3811
+ const withoutExtension = logicalPath.slice(0, -3);
3812
+ return exclude.find((pattern) => isExcluded([pattern], withoutExtension));
3680
3813
  }
3681
3814
  function normalizeLogicalPath(logicalPath) {
3682
3815
  const normalizedPath = normalizeContentPath(logicalPath);
@@ -3700,53 +3833,22 @@ function missingContentDirectoryError(directory) {
3700
3833
  return error;
3701
3834
  }
3702
3835
 
3703
- // src/content/content_links.ts
3704
- var WIKILINK_PATTERN = /(!)?\[\[([^\]|#^]+)(?:[#^][^\]|]+)?(?:\|[^\]]+)?\]\]/g;
3705
- function extractContentLinks(markdown, contentIndex) {
3706
- WIKILINK_PATTERN.lastIndex = 0;
3707
- const links = [];
3708
- for (let match = WIKILINK_PATTERN.exec(markdown); match !== null; match = WIKILINK_PATTERN.exec(markdown)) {
3709
- const rawTarget = match[2]?.trim();
3710
- if (!rawTarget) continue;
3711
- const resolved = contentIndex.get(rawTarget.toLowerCase()) ?? null;
3712
- links.push({
3713
- raw: rawTarget,
3714
- slug: resolved,
3715
- kind: resolved ? linkKind(resolved) : "unresolved",
3716
- embed: match[1] === "!"
3717
- });
3718
- }
3719
- return links;
3720
- }
3721
- function linkKind(value) {
3722
- if (isImagePath(value)) return "image";
3723
- if (isAttachmentPath(value)) return "attachment";
3724
- return "note";
3725
- }
3726
-
3727
3836
  // src/content/content_stable_id.ts
3728
3837
  var MAX_STABLE_CONTENT_ID_LENGTH = 160;
3729
3838
  function resolveContentStableId(frontmatter) {
3730
- const id = readOptionalStableId(frontmatter.id, "id");
3731
- const uid = readOptionalStableId(frontmatter.uid, "uid");
3732
- if (id !== void 0 && uid !== void 0 && id !== uid) {
3733
- throw new Error(
3734
- 'Content frontmatter fields "id" and "uid" must have the same value.'
3735
- );
3736
- }
3737
- return id ?? uid;
3839
+ return readOptionalStableId(frontmatter.id);
3738
3840
  }
3739
- function readOptionalStableId(value, field) {
3841
+ function readOptionalStableId(value) {
3740
3842
  if (value === void 0) return void 0;
3741
3843
  if (typeof value !== "string") {
3742
- throw new Error(`Content frontmatter field "${field}" must be a string.`);
3844
+ throw new Error(`Content frontmatter field "id" must be a string.`);
3743
3845
  }
3744
3846
  if (value.length === 0 || value.length > MAX_STABLE_CONTENT_ID_LENGTH || value.trim() !== value || [...value].some((character) => {
3745
3847
  const code = character.codePointAt(0) ?? 0;
3746
3848
  return code <= 31 || code === 127;
3747
3849
  })) {
3748
3850
  throw new Error(
3749
- `Content frontmatter field "${field}" must be a non-empty, trimmed string of at most ${MAX_STABLE_CONTENT_ID_LENGTH} characters without control characters.`
3851
+ `Content frontmatter field "id" must be a non-empty, trimmed string of at most ${MAX_STABLE_CONTENT_ID_LENGTH} characters without control characters.`
3750
3852
  );
3751
3853
  }
3752
3854
  return value;
@@ -3766,6 +3868,7 @@ var ManifestBuilder = class {
3766
3868
  permalink: location.permalink,
3767
3869
  publicLocation: location,
3768
3870
  title: getManifestTitle(slug, processed.frontmatter.title),
3871
+ aliases: extractFrontmatterAliases(markdown),
3769
3872
  frontmatter: processed.frontmatter,
3770
3873
  publishing,
3771
3874
  html: processed.html,
@@ -3781,6 +3884,7 @@ var ManifestBuilder = class {
3781
3884
  build(entries, contentIndex) {
3782
3885
  const bySlug = new Map(entries.map((entry) => [entry.slug, entry]));
3783
3886
  const byContentId = createContentIdIndex(entries);
3887
+ const byAlias = createAliasIndex(entries);
3784
3888
  const byPermalink = new Map(
3785
3889
  entries.map((entry) => [entry.permalink, entry])
3786
3890
  );
@@ -3808,6 +3912,7 @@ var ManifestBuilder = class {
3808
3912
  discoverableEntries: [...entries],
3809
3913
  bySlug,
3810
3914
  byContentId,
3915
+ byAlias,
3811
3916
  byPermalink,
3812
3917
  byRoutablePermalink: new Map(byPermalink),
3813
3918
  redirects: /* @__PURE__ */ new Map(),
@@ -3828,6 +3933,14 @@ var ManifestBuilder = class {
3828
3933
  return { ...manifest, graph: createContentGraph(manifest) };
3829
3934
  }
3830
3935
  };
3936
+ function createAliasIndex(entries) {
3937
+ const byAlias = /* @__PURE__ */ new Map();
3938
+ for (const entry of entries) {
3939
+ for (const alias of entry.aliases ?? [])
3940
+ appendToMap(byAlias, alias.toLowerCase(), entry);
3941
+ }
3942
+ return byAlias;
3943
+ }
3831
3944
  function createContentIdIndex(entries) {
3832
3945
  const byContentId = /* @__PURE__ */ new Map();
3833
3946
  for (const entry of entries) {
@@ -4042,8 +4155,20 @@ function linkTargets(entry) {
4042
4155
  ).map((link) => link.slug);
4043
4156
  }
4044
4157
  function addOutput(outputs, output) {
4158
+ const existing = outputs.get(output.path);
4159
+ if (existing) {
4160
+ assertNoGeneratedOutputCollision(existing, output);
4161
+ }
4045
4162
  outputs.set(output.path, output);
4046
4163
  }
4164
+ function assertNoGeneratedOutputCollision(existing, incoming) {
4165
+ if (existing.kind !== "generated" && incoming.kind !== "generated") return;
4166
+ if (existing.kind === incoming.kind && existing.producer === incoming.producer)
4167
+ return;
4168
+ throw new Error(
4169
+ `Generated output path "${incoming.path}" collides with ${existing.kind} output owned by "${existing.producer}" and ${incoming.kind} output owned by "${incoming.producer}".`
4170
+ );
4171
+ }
4047
4172
  function normalizePublicPath(pathname) {
4048
4173
  const path5 = `/${pathname.split("/").filter(Boolean).join("/")}`;
4049
4174
  return path5 === "/" ? path5 : path5.replace(/\/$/, "");
@@ -4123,9 +4248,14 @@ var CONTENT_PROCESSING_CONCURRENCY = 16;
4123
4248
  var ContentManager = class {
4124
4249
  constructor(content, exclude = [], pipelineOptions = {}) {
4125
4250
  this.pipelineOptions = pipelineOptions;
4251
+ const config = pipelineOptions.config ? {
4252
+ ...pipelineOptions.config,
4253
+ plugins: pipelineOptions.plugins ?? pipelineOptions.config.plugins
4254
+ } : void 0;
4126
4255
  this.pipelineOptions = {
4127
4256
  ...pipelineOptions,
4128
- plugins: pipelineOptions.plugins ?? pipelineOptions.config?.plugins,
4257
+ config,
4258
+ plugins: pipelineOptions.plugins ?? config?.plugins,
4129
4259
  isRoutable: (slug) => this.isRoutable(slug)
4130
4260
  };
4131
4261
  this.publishingBuildTime = resolvePublishingBuildTime(
@@ -4603,6 +4733,7 @@ function cloneManifestEntry(entry) {
4603
4733
  ...entry,
4604
4734
  publicLocation: { ...entry.publicLocation },
4605
4735
  frontmatter: { ...entry.frontmatter },
4736
+ aliases: [...entry.aliases ?? []],
4606
4737
  tags: [...entry.tags],
4607
4738
  links: entry.links.map((link) => ({ ...link })),
4608
4739
  backlinks: [...entry.backlinks],
@@ -4669,6 +4800,17 @@ function resolveFolderLocation(manifest, folder) {
4669
4800
  const location = manifest.folderLocations.get(normalizeFolder2(folder));
4670
4801
  return location ? { type: "generated", pathname: location.pathname } : { type: "none" };
4671
4802
  }
4803
+ function resolvePublicFolderLocation(manifest, pathname) {
4804
+ const normalizedPathname = normalizePathname(pathname);
4805
+ const entry = manifest.publicEntries.find(
4806
+ (candidate) => normalizePathname(candidate.publicLocation.permalink) === normalizedPathname
4807
+ );
4808
+ if (entry) return { type: "content", entry };
4809
+ const location = [...manifest.folderLocations.values()].find(
4810
+ (candidate) => normalizePathname(candidate.pathname) === normalizedPathname
4811
+ );
4812
+ return location ? { type: "generated", pathname: location.pathname } : { type: "none" };
4813
+ }
4672
4814
  function resolveGeneratedFolderLocation(manifest, folder) {
4673
4815
  const normalizedFolder = normalizeFolder2(folder);
4674
4816
  if (!normalizedFolder) return null;
@@ -4701,6 +4843,10 @@ function isInScope(entry, scope) {
4701
4843
  function normalizeFolder2(folder) {
4702
4844
  return folder.split("/").filter(Boolean).join("/");
4703
4845
  }
4846
+ function normalizePathname(pathname) {
4847
+ const segments = pathname.split("/").filter(Boolean);
4848
+ return segments.length === 0 ? "/" : `/${segments.join("/")}`;
4849
+ }
4704
4850
  function parentPath(pathname, levels) {
4705
4851
  const segments = pathname.split("/").filter(Boolean);
4706
4852
  if (segments.length < levels) return null;
@@ -4909,6 +5055,7 @@ function createGraphEntry(entry, markdown, contentIndex, locations) {
4909
5055
  permalink: location.permalink,
4910
5056
  publicLocation: location,
4911
5057
  title: slug,
5058
+ aliases: [],
4912
5059
  frontmatter: {},
4913
5060
  publishing: resolvePublishingState(
4914
5061
  {},
@@ -5001,6 +5148,7 @@ export {
5001
5148
  buildOutputInventory,
5002
5149
  calculateReadingTime,
5003
5150
  collectConfigDeprecationDiagnostics,
5151
+ computePipelineFingerprint,
5004
5152
  createClientEntry,
5005
5153
  createContentGraph,
5006
5154
  createDeprecationDiagnostic,
@@ -5019,6 +5167,7 @@ export {
5019
5167
  fingerprintContentEntries,
5020
5168
  formatDeprecationMessage,
5021
5169
  getContentSourceEntry,
5170
+ getEntryLanguage,
5022
5171
  getExtension,
5023
5172
  getResolvedPluginMetadata,
5024
5173
  groupContentEntries,
@@ -5028,11 +5177,13 @@ export {
5028
5177
  isIgnoredContentPath,
5029
5178
  isImagePath,
5030
5179
  isMarkdownPath,
5180
+ isPersistentlyCacheable,
5031
5181
  isPublishable,
5032
5182
  isPublished,
5033
5183
  layoutForceGraph,
5034
5184
  layoutRadialGraph,
5035
5185
  loadContentBuildState,
5186
+ matchContentExcludePattern,
5036
5187
  normalizeContentAssetOutputPath,
5037
5188
  normalizeContentPath,
5038
5189
  normalizeGeneratedOutputPath,
@@ -5052,8 +5203,10 @@ export {
5052
5203
  resolveFolderLocation,
5053
5204
  resolveGeneratedFolderLocation,
5054
5205
  resolvePlugins,
5206
+ resolvePublicFolderLocation,
5055
5207
  resolvePublishingBuildTime,
5056
5208
  resolvePublishingState,
5209
+ selectEntriesByLanguage,
5057
5210
  serializePublicClientConfig,
5058
5211
  stableStringify,
5059
5212
  stripHtml,
@@ -3,7 +3,7 @@ import type { ContentManifest, ContentManifestEntry } from "../types/content_man
3
3
  import { determineAffectedContent } from "./affected_content.js";
4
4
  import { type ContentBuildState, type FingerprintedContentEntry } from "./content_build_state.js";
5
5
  import { type ContentChangeSet } from "./content_change_set.js";
6
- import type { CachedContentDependency } from "./content_dependency_tracker.js";
6
+ import { type CachedContentDependency } from "./content_dependency_tracker.js";
7
7
  import type { ContentSourceEntry } from "./content_source.js";
8
8
  import type { OutputDescriptor } from "./output_dependency.js";
9
9
  export type ContentBuildPreparation = {
@@ -1,7 +1,7 @@
1
1
  import type { ContentManifestEntry } from "../types/content_manifest.js";
2
2
  import type { ContentSourceEntry } from "./content_source.js";
3
3
  import type { OutputDescriptor } from "./output_dependency.js";
4
- export declare const CONTENT_BUILD_STATE_VERSION = 6;
4
+ export declare const CONTENT_BUILD_STATE_VERSION = 7;
5
5
  /**
6
6
  * Build state lives under `<content directory>/.riebeckite`. That directory is
7
7
  * build-time state, not content, so content sources must exclude it from scans
@@ -1,4 +1,6 @@
1
1
  import type { ContentSource, ContentSourceContent } from "./content_source.js";
2
+ export declare const LINK_INDEX_PREFIX = "index:";
3
+ export declare const LINK_LOCATION_PREFIX = "location:";
2
4
  export type CachedContentDependency = {
3
5
  readonly kind: "content" | "file" | "link";
4
6
  readonly id: string;
@@ -0,0 +1,3 @@
1
+ import type { ContentManifestEntry } from "../types/content_manifest.js";
2
+ export declare function getEntryLanguage(entry: ContentManifestEntry): string | undefined;
3
+ export declare function selectEntriesByLanguage(entries: readonly ContentManifestEntry[], language: string | undefined): readonly ContentManifestEntry[];
@@ -1,8 +1,5 @@
1
1
  import type { PostFrontmatter } from "../types/post_content.js";
2
2
  /**
3
- * Reads the optional, source-authored stable content ID.
4
- *
5
- * `id` is the canonical frontmatter field. `uid` is accepted only for
6
- * compatibility with existing content that used it before `id` was standard.
3
+ * Reads the optional, source-authored stable content ID from frontmatter.
7
4
  */
8
5
  export declare function resolveContentStableId(frontmatter: PostFrontmatter): string | undefined;
@@ -1,13 +1,22 @@
1
1
  import type { ContentSource, ContentSourceContent, ContentSourceEntry } from "./content_source.js";
2
2
  export declare const INTERNAL_CONTENT_IGNORE_PATTERNS: readonly [".git", ".git/**", ".github", ".github/**", ".obsidian", ".obsidian/**", "node_modules", "node_modules/**", "**/.DS_Store", "**/Thumbs.db", "**/desktop.ini"];
3
+ export type ContentSourceExclusion = {
4
+ readonly path: string;
5
+ readonly pattern: string;
6
+ };
7
+ export type ContentSourceScan = {
8
+ readonly entries: readonly ContentSourceEntry[];
9
+ readonly exclusions: readonly ContentSourceExclusion[];
10
+ };
3
11
  export declare class FileSystemContentSource implements ContentSource {
4
12
  private contentDirectory;
5
13
  private exclude;
6
14
  constructor(contentDirectory: string, exclude?: readonly string[]);
7
15
  scan(): Promise<readonly ContentSourceEntry[]>;
16
+ scanWithExclusions(): Promise<ContentSourceScan>;
8
17
  read(entry: ContentSourceEntry): Promise<ContentSourceContent>;
9
18
  private scanDirectory;
10
- private isExcluded;
11
19
  private resolveFilePath;
12
20
  }
13
21
  export declare function isIgnoredContentPath(logicalPath: string, exclude?: readonly string[]): boolean;
22
+ export declare function matchContentExcludePattern(logicalPath: string, exclude?: readonly string[]): string | undefined;
@@ -32,4 +32,5 @@ export type FolderLocationResolution = {
32
32
  };
33
33
  export declare function resolveFolderEntry(manifest: ContentManifest, folder: string, options?: ResolveFolderEntryOptions): FolderEntryResolution;
34
34
  export declare function resolveFolderLocation(manifest: ContentManifest, folder: string): FolderLocationResolution;
35
+ export declare function resolvePublicFolderLocation(manifest: ContentManifest, pathname: string): FolderLocationResolution;
35
36
  export declare function resolveGeneratedFolderLocation(manifest: ContentManifest, folder: string): string | null;
@@ -29,6 +29,7 @@ export declare class Pipeline {
29
29
  private reportContentDependencies;
30
30
  private getPermalink;
31
31
  private resolveLinkDependency;
32
+ private recordNoteLinkDependencies;
32
33
  private use;
33
34
  private createNoteEmbedRenderer;
34
35
  private resolvePlugins;
@@ -1,11 +1,13 @@
1
+ import type { Tracer } from "../observability.js";
1
2
  import type { RiebeckitePlugin } from "../types/plugin.js";
2
3
  import type { PluginLifecycleContext, PluginManifestContext } from "../types/plugin_context.js";
3
- type LifecycleHookName = "setup" | "buildStart" | "buildEnd" | "dispose";
4
- export declare class PluginLifecycleError extends Error {
5
- constructor(pluginName: string, hookName: LifecycleHookName, cause: unknown);
4
+ export declare class PluginHookError extends Error {
5
+ constructor(pluginName: string, hookName: string, cause: unknown);
6
6
  }
7
+ export declare function runPluginHook<TContext extends {
8
+ tracer: Tracer;
9
+ }, TResult>(plugin: RiebeckitePlugin, hookName: string, context: TContext, hook: (context: TContext) => TResult | Promise<TResult>): Promise<TResult>;
7
10
  export declare function runSetup(plugins: readonly RiebeckitePlugin[], createContext: (plugin: RiebeckitePlugin) => PluginLifecycleContext): Promise<void>;
8
11
  export declare function runBuildStart(plugins: readonly RiebeckitePlugin[], createContext: (plugin: RiebeckitePlugin) => PluginLifecycleContext): Promise<void>;
9
12
  export declare function runBuildEnd(plugins: readonly RiebeckitePlugin[], createContext: (plugin: RiebeckitePlugin) => PluginManifestContext): Promise<void>;
10
13
  export declare function runDispose(plugins: readonly RiebeckitePlugin[], createContext: (plugin: RiebeckitePlugin) => PluginLifecycleContext): Promise<void>;
11
- export {};
@@ -12,7 +12,7 @@ import type { PostContent } from "../types/post_content.js";
12
12
  * requested yet; the remaining values name the stage currently executing.
13
13
  * Once `started` is reached the lifecycle never runs again.
14
14
  */
15
- export type PluginBuildStage = "idle" | "setup" | "buildStart" | "onBuildStart" | "onConfigResolved" | "started";
15
+ export type PluginBuildStage = "idle" | "setup" | "buildStart" | "onConfigResolved" | "started";
16
16
  export declare class PluginRuntime {
17
17
  private pipelineOptions;
18
18
  private buildLifecycle;
@@ -23,6 +23,7 @@ export declare class PluginRuntime {
23
23
  private generatedOutputs;
24
24
  private isBuildTime;
25
25
  private resolvedPlugins;
26
+ private pageDeclarations;
26
27
  constructor(pipelineOptions?: PipelineOptions);
27
28
  getDiagnostics(): Diagnostic[];
28
29
  getBuildStage(): PluginBuildStage;
@@ -30,7 +31,7 @@ export declare class PluginRuntime {
30
31
  * Starts the plugin build lifecycle exactly once, regardless of which public
31
32
  * entry point (content processing or location resolution) reaches it first.
32
33
  * Concurrent callers await the same run, so the ordered hooks
33
- * (`setup` → `buildStart` → `onBuildStart` → `onConfigResolved`) are never
34
+ * (`setup` → `buildStart` → `onConfigResolved`) are never
34
35
  * duplicated and no caller proceeds before the lifecycle completes.
35
36
  */
36
37
  startBuild(contentIndex: Map<string, string>): Promise<void>;
@@ -61,6 +62,8 @@ export declare class PluginRuntime {
61
62
  getPagePaths(manifest: ContentManifest, contentIndex: Map<string, string>): Promise<readonly string[]>;
62
63
  getPageRoutes(manifest: ContentManifest, contentIndex: Map<string, string>): Promise<readonly PluginPageRoute[]>;
63
64
  getPageOutputs(manifest: ContentManifest, contentIndex: Map<string, string>): Promise<readonly OutputDescriptor[]>;
65
+ private getPageDeclarations;
66
+ private resolvePageDeclarations;
64
67
  getContentOutputDependencies(): readonly OutputDependency[];
65
68
  collectDiagnostics(contentIndex: Map<string, string>): Promise<Diagnostic[]>;
66
69
  private runBuildLifecycle;
@@ -24,7 +24,7 @@ export type ContentManifestPluginAsset = PluginAsset & {
24
24
  * The standard article consumer recognizes the `article.*` positions below.
25
25
  * Other names remain valid so a custom Site can define its own layout slots.
26
26
  */
27
- export type ContentBodySlot = "article.after-header" | "article.after-meta" | "article.aside" | "article.before-content" | "article.after-content" | "article.footer" | (string & {});
27
+ export type ContentBodySlot = "article.header" | "article.metadata" | "article.aside" | "article.before-content" | "article.after-content" | "article.footer" | (string & {});
28
28
  /** Client entries and their explicitly public configuration. */
29
29
  export type ContentManifestPluginClientEntry = PluginClientEntry;
30
30
  export type ContentManifestEntry = {
@@ -37,6 +37,7 @@ export type ContentManifestEntry = {
37
37
  permalink: string;
38
38
  publicLocation: ContentPublicLocation;
39
39
  title: string;
40
+ aliases?: readonly string[];
40
41
  frontmatter: PostFrontmatter;
41
42
  publishing: ResolvedPublishingState;
42
43
  html: string;
@@ -70,6 +71,7 @@ export type ContentRedirect = {
70
71
  export type ContentPublicLocation = {
71
72
  slug: string;
72
73
  permalink: string;
74
+ language?: string;
73
75
  redirects?: readonly ContentRedirect[];
74
76
  metadata?: Readonly<Record<string, string>>;
75
77
  };
@@ -89,9 +91,10 @@ export type ContentLocationInput = {
89
91
  export type ContentManifest = {
90
92
  entries: ContentManifestEntry[];
91
93
  /**
92
- * Entries that pass the configured publish strategy. `entries` keeps every
93
- * scanned note for backward compatibility; publishing plugins should read
94
- * this view so unpublished notes never reach generated output.
94
+ * Entries that are routable and therefore get a generated page: `public`
95
+ * and `unlisted` notes. `entries` keeps every scanned note for backward
96
+ * compatibility; plugins that emit generated output should read this view
97
+ * so drafts and scheduled notes never reach generated output.
95
98
  */
96
99
  publicEntries: ContentManifestEntry[];
97
100
  /** Entries that may appear on discovery surfaces such as navigation, search, feeds, taxonomy, and public graphs. */
@@ -99,6 +102,7 @@ export type ContentManifest = {
99
102
  bySlug: Map<string, ContentManifestEntry>;
100
103
  /** Contains only entries with an explicit source-authored content ID. */
101
104
  byContentId: Map<string, ContentManifestEntry>;
105
+ byAlias: Map<string, ContentManifestEntry[]>;
102
106
  byPermalink: Map<string, ContentManifestEntry>;
103
107
  /** Contains only routable entries: public and unlisted, excluding draft and scheduled-before entries. */
104
108
  byRoutablePermalink: Map<string, ContentManifestEntry>;
@@ -59,8 +59,6 @@ export type RiebeckitePlugin<TOptions = unknown> = {
59
59
  onPostParsed?(context: PluginPostContext): void | Promise<void>;
60
60
  onPostProcessed?(context: PluginPostContext): void | Promise<void>;
61
61
  onManifestCreated?(context: PluginManifestContext): void | Promise<void>;
62
- onBuildStart?(context: PluginContext): void | Promise<void>;
63
- onBuildEnd?(context: PluginManifestContext): void | Promise<void>;
64
62
  extendMarkdownPipeline?(pipeline: MarkdownPipeline, context: MarkdownPipelineContext): void;
65
63
  extendHtmlPipeline?(pipeline: HtmlPipeline): void;
66
64
  addDiagnostics?(context: PluginContext): Diagnostic[] | Promise<Diagnostic[]>;
@@ -14,6 +14,8 @@ export type PluginPage = {
14
14
  description?: string;
15
15
  /** Optional document metadata rendered by the site's document frame. */
16
16
  headTags?: readonly PluginHeadTag[];
17
+ /** Language of the page when the page type resolved one. */
18
+ language?: string;
17
19
  };
18
20
  export type PluginPageContext = PluginContext & {
19
21
  /** The public manifest. Unpublished content is not exposed through pages. */
@@ -1,4 +1,5 @@
1
1
  import type { ContentManifestEntry } from "./content_manifest.js";
2
+ import type { PluginHeadTag } from "./plugin_head.js";
2
3
  import type { PostContent } from "./post_content.js";
3
4
  import type { ResolvedRiebeckiteConfig } from "./resolved_riebeckite_config.js";
4
5
  export type SeoMetadata = {
@@ -24,8 +25,8 @@ export type RenderableFeedEntry = ContentManifestEntry & {
24
25
  html?: string;
25
26
  };
26
27
  export type PluginSeoExtension = {
27
- buildArticleSeo(config: ResolvedRiebeckiteConfig, permalink: string, post: PostContent): SeoMetadata;
28
- buildWebsiteSeo(config: ResolvedRiebeckiteConfig, input: WebsiteSeoInput): SeoMetadata;
28
+ buildArticleSeo(config: ResolvedRiebeckiteConfig, permalink: string, post: PostContent, headTags?: readonly PluginHeadTag[]): SeoMetadata;
29
+ buildWebsiteSeo(config: ResolvedRiebeckiteConfig, input: WebsiteSeoInput, headTags?: readonly PluginHeadTag[]): SeoMetadata;
29
30
  buildAbsoluteUrl(config: ResolvedRiebeckiteConfig, pathOrUrl: string): string;
30
31
  buildPostUrl(config: ResolvedRiebeckiteConfig, permalink: string): string;
31
32
  getDescription(post: Pick<PostContent, "frontmatter" | "html">): string;
@@ -8,8 +8,6 @@ export type PostFrontmatter = Record<string, unknown> & {
8
8
  * this value survives a rename or public-location change.
9
9
  */
10
10
  id?: string;
11
- /** @deprecated Use `id`. Read only as a compatibility fallback. */
12
- uid?: string;
13
11
  title?: string;
14
12
  description?: string;
15
13
  date?: string | Date;
@@ -9,7 +9,13 @@ export type ResolvedRiebeckiteConfig = {
9
9
  * relative to their application root.
10
10
  */
11
11
  buildDirectory?: string;
12
- site: Required<SiteConfig>;
12
+ site: Omit<Required<SiteConfig>, "feed"> & {
13
+ feed: {
14
+ title: string;
15
+ description: string;
16
+ language: string;
17
+ };
18
+ };
13
19
  content: {
14
20
  directory: string;
15
21
  source?: ContentSource;
@@ -18,11 +24,6 @@ export type ResolvedRiebeckiteConfig = {
18
24
  publishStrategy: PublishStrategy;
19
25
  };
20
26
  };
21
- markdown: {
22
- syntaxHighlight: {
23
- theme: string;
24
- };
25
- };
26
27
  theme: Required<Omit<ThemeConfig, "tokens">> & {
27
28
  tokens: NonNullable<ThemeConfig["tokens"]>;
28
29
  styles: ThemeStyle[];
@@ -13,11 +13,6 @@ export interface RiebeckiteConfig {
13
13
  publishStrategy?: PublishStrategy;
14
14
  };
15
15
  };
16
- markdown?: {
17
- syntaxHighlight?: {
18
- theme?: string;
19
- };
20
- };
21
16
  theme?: ThemeInput;
22
17
  plugins?: PluginInput[];
23
18
  cache?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riebeckite/core",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "devDependencies": {
51
51
  "tsx": "^4.22.4",
52
- "@riebeckite/test": "0.0.15"
52
+ "@riebeckite/test": "0.0.17"
53
53
  },
54
54
  "repository": {
55
55
  "type": "git",