@riebeckite/core 0.0.16 → 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,16 +10,19 @@ 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
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";
@@ -46,7 +49,6 @@ export type { Diagnostic, DiagnosticCode, DiagnosticSeverity, } from "./src/type
46
49
  export type { ContentAssetOutputInput, GeneratedOutput, GeneratedOutputContent, GeneratedOutputInput, GeneratedOutputSink, } from "./src/types/generated_output.js";
47
50
  export { createUnavailableGeneratedOutputSink, normalizeContentAssetOutputPath, normalizeGeneratedOutputPath, } from "./src/types/generated_output.js";
48
51
  export type { JsonValue } from "./src/types/json_value.js";
49
- export type { NavigationConfig, NavigationItem, } from "./src/types/navigation_config.js";
50
52
  export type { OutputDependency } from "./src/types/output_dependency.js";
51
53
  export type { PluginInput, ResolvedPluginMetadata, RiebeckitePlugin, } from "./src/types/plugin.js";
52
54
  export { definePlugin, getResolvedPluginMetadata, resolvePlugins, } from "./src/types/plugin.js";
package/dist/index.js CHANGED
@@ -18,55 +18,12 @@ function validateConfigIssues(config) {
18
18
  return [{ path: "config", message: "Expected an object." }];
19
19
  }
20
20
  validateSite(value.site, issues);
21
- validateNavigation(value.navigation, issues);
22
21
  validateContent(value.content, issues);
22
+ validateCache(value.cache, issues);
23
23
  validateTheme(value.theme, issues);
24
24
  validatePlugins(value.plugins, issues);
25
25
  return issues;
26
26
  }
27
- function validateNavigation(value, issues) {
28
- if (value === void 0) return;
29
- if (!isRecord(value)) {
30
- issues.push({ path: "navigation", message: "Expected an object." });
31
- return;
32
- }
33
- validateNavigationItems(value.header, "navigation.header", issues, /* @__PURE__ */ new Set());
34
- validateNavigationItems(value.footer, "navigation.footer", issues, /* @__PURE__ */ new Set());
35
- }
36
- function validateNavigationItems(value, path5, issues, ancestors) {
37
- if (value === void 0) return;
38
- if (!Array.isArray(value)) {
39
- issues.push({ path: path5, message: "Expected an array." });
40
- return;
41
- }
42
- for (const [index, item] of value.entries()) {
43
- const itemPath = `${path5}[${index}]`;
44
- if (!isRecord(item)) {
45
- issues.push({ path: itemPath, message: "Expected an object." });
46
- continue;
47
- }
48
- if (ancestors.has(item)) {
49
- issues.push({
50
- path: itemPath,
51
- message: "Navigation children must not be recursive."
52
- });
53
- continue;
54
- }
55
- validateRequiredString(item.label, `${itemPath}.label`, issues);
56
- validateRequiredString(item.href, `${itemPath}.href`, issues);
57
- validateOptionalBoolean(item.external, `${itemPath}.external`, issues);
58
- if (item.children !== void 0) {
59
- const nextAncestors = new Set(ancestors);
60
- nextAncestors.add(item);
61
- validateNavigationItems(
62
- item.children,
63
- `${itemPath}.children`,
64
- issues,
65
- nextAncestors
66
- );
67
- }
68
- }
69
- }
70
27
  function validateSite(value, issues) {
71
28
  if (!isRecord(value)) {
72
29
  issues.push({ path: "site", message: "Expected an object." });
@@ -137,6 +94,15 @@ function validatePublishStrategy(value, issues) {
137
94
  });
138
95
  }
139
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
+ }
140
106
  function validateTheme(value, issues) {
141
107
  if (value === void 0) return;
142
108
  if (!isRecord(value)) {
@@ -669,23 +635,14 @@ function resolveConfig(config) {
669
635
  language: config.site?.feed?.language ?? config.site?.locale ?? "en"
670
636
  }
671
637
  },
672
- navigation: {
673
- header: config.navigation?.header ?? [],
674
- footer: config.navigation?.footer ?? []
675
- },
676
638
  content: {
677
- directory: config.content?.directory ?? "../../content",
639
+ directory: config.content?.directory ?? "content",
678
640
  source: config.content?.source,
679
641
  exclude: config.content?.exclude ?? [],
680
642
  filters: {
681
643
  publishStrategy: config.content?.filters?.publishStrategy ?? "explicit"
682
644
  }
683
645
  },
684
- markdown: {
685
- syntaxHighlight: {
686
- theme: config.markdown?.syntaxHighlight?.theme ?? ""
687
- }
688
- },
689
646
  theme,
690
647
  plugins: resolvePlugins(config.plugins),
691
648
  cache: {
@@ -853,7 +810,7 @@ import fs from "node:fs/promises";
853
810
  import path from "node:path";
854
811
 
855
812
  // src/content/content_build_state.ts
856
- var CONTENT_BUILD_STATE_VERSION = 6;
813
+ var CONTENT_BUILD_STATE_VERSION = 7;
857
814
  var CONTENT_BUILD_STATE_DIRECTORY = ".riebeckite";
858
815
  var CONTENT_BUILD_STATE_EXCLUDE = `${CONTENT_BUILD_STATE_DIRECTORY}/**`;
859
816
 
@@ -1370,6 +1327,23 @@ function isResolvedNoteLink(link) {
1370
1327
  return link.kind === "note" && link.slug !== null;
1371
1328
  }
1372
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
+
1373
1347
  // src/content/content_location.ts
1374
1348
  function resolveDefaultContentLocation(content) {
1375
1349
  return {
@@ -1527,6 +1501,8 @@ import { matter as matter2 } from "vfile-matter";
1527
1501
 
1528
1502
  // src/content/content_dependency_tracker.ts
1529
1503
  import { createHash as createHash2 } from "node:crypto";
1504
+ var LINK_INDEX_PREFIX = "index:";
1505
+ var LINK_LOCATION_PREFIX = "location:";
1530
1506
  function createContentDependencyTracker(source) {
1531
1507
  const dependencies = /* @__PURE__ */ new Map();
1532
1508
  const record = (identity, content) => {
@@ -1572,6 +1548,30 @@ function dependencyKey(dependency) {
1572
1548
  return `${normalized.kind}:${normalized.id}`;
1573
1549
  }
1574
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
+
1575
1575
  // src/content/content_persistent_cache.ts
1576
1576
  import { createHash as createHash4 } from "node:crypto";
1577
1577
  import fs2 from "node:fs/promises";
@@ -1768,7 +1768,6 @@ function computePipelineFingerprint(config) {
1768
1768
  const fingerprintData = {
1769
1769
  compatibilityVersion: CONTENT_CACHE_COMPATIBILITY_VERSION,
1770
1770
  plugins: plugins.map((p) => pluginFingerprint(p)),
1771
- markdown: config.markdown,
1772
1771
  content: {
1773
1772
  filters: config.content.filters
1774
1773
  }
@@ -1933,6 +1932,7 @@ async function writeAtomically2(targetPath, versionDirectory2, serialized) {
1933
1932
  }
1934
1933
  function resolvePluginCacheDirectory(config) {
1935
1934
  if (!config) return path3.resolve(process.cwd(), ".riebeckite", "cache");
1935
+ if (config.cache?.directory) return config.cache.directory;
1936
1936
  if (config.buildDirectory) return path3.join(config.buildDirectory, "cache");
1937
1937
  return path3.resolve(config.content.directory, "..", ".riebeckite", "cache");
1938
1938
  }
@@ -2201,6 +2201,9 @@ var Pipeline = class {
2201
2201
  frontmatter: file.data.matter || {},
2202
2202
  html: String(file.value)
2203
2203
  };
2204
+ if (embedDepth === 0 && sourceSlug && dependencyTracker) {
2205
+ this.recordNoteLinkDependencies(markDownContent, dependencyTracker);
2206
+ }
2204
2207
  if (sourceSlug && dependencyTracker) {
2205
2208
  this.reportContentDependencies(
2206
2209
  sourceSlug,
@@ -2250,6 +2253,25 @@ var Pipeline = class {
2250
2253
  this.options.isRoutable
2251
2254
  );
2252
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
+ }
2253
2275
  use(processor, plugin, options) {
2254
2276
  const register = processor.use.bind(processor);
2255
2277
  if (options === void 0) {
@@ -2353,8 +2375,6 @@ function normalizeMarkdownLinks(context) {
2353
2375
  });
2354
2376
  };
2355
2377
  }
2356
- var LINK_INDEX_PREFIX = "index:";
2357
- var LINK_LOCATION_PREFIX = "location:";
2358
2378
  function resolveLinkDependencyValue(id, contentIndex, lookupPermalink, isRoutable) {
2359
2379
  if (id.startsWith(LINK_INDEX_PREFIX)) {
2360
2380
  return contentIndex.get(id.slice(LINK_INDEX_PREFIX.length)) ?? "";
@@ -2565,12 +2585,26 @@ var GeneratedOutputRegistry = class {
2565
2585
  };
2566
2586
 
2567
2587
  // src/plugin/plugin_lifecycle.ts
2568
- var PluginLifecycleError = class extends Error {
2588
+ var PluginHookError = class extends Error {
2569
2589
  constructor(pluginName, hookName, cause) {
2570
2590
  super(`Plugin "${pluginName}" failed during "${hookName}"`, { cause });
2571
- this.name = "PluginLifecycleError";
2591
+ this.name = "PluginHookError";
2572
2592
  }
2573
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
+ }
2574
2608
  async function runSetup(plugins, createContext) {
2575
2609
  await runLifecycleHook(
2576
2610
  plugins,
@@ -2607,19 +2641,7 @@ async function runLifecycleHook(plugins, hookName, getHook, createContext) {
2607
2641
  for (const plugin of plugins) {
2608
2642
  const hook = getHook(plugin);
2609
2643
  if (!hook) continue;
2610
- try {
2611
- const context = createContext(plugin);
2612
- await context.tracer.span(
2613
- `plugin.${hookName}`,
2614
- {
2615
- plugin: plugin.name,
2616
- hook: hookName
2617
- },
2618
- () => hook(context)
2619
- );
2620
- } catch (error) {
2621
- throw new PluginLifecycleError(plugin.name, hookName, error);
2622
- }
2644
+ await runPluginHook(plugin, hookName, createContext(plugin), hook);
2623
2645
  }
2624
2646
  }
2625
2647
 
@@ -2637,6 +2659,7 @@ var PluginRuntime = class {
2637
2659
  generatedOutputs = new GeneratedOutputRegistry();
2638
2660
  isBuildTime = false;
2639
2661
  resolvedPlugins = null;
2662
+ pageDeclarations = /* @__PURE__ */ new WeakMap();
2640
2663
  getDiagnostics() {
2641
2664
  return this.diagnostics;
2642
2665
  }
@@ -2647,7 +2670,7 @@ var PluginRuntime = class {
2647
2670
  * Starts the plugin build lifecycle exactly once, regardless of which public
2648
2671
  * entry point (content processing or location resolution) reaches it first.
2649
2672
  * Concurrent callers await the same run, so the ordered hooks
2650
- * (`setup` → `buildStart` → `onBuildStart` → `onConfigResolved`) are never
2673
+ * (`setup` → `buildStart` → `onConfigResolved`) are never
2651
2674
  * duplicated and no caller proceeds before the lifecycle completes.
2652
2675
  */
2653
2676
  startBuild(contentIndex) {
@@ -2655,7 +2678,7 @@ var PluginRuntime = class {
2655
2678
  return this.buildLifecycle;
2656
2679
  }
2657
2680
  async runContentLoaded(slug, markdown, contentIndex) {
2658
- await this.runHook((plugin) => plugin.onContentLoaded, {
2681
+ await this.runHook("onContentLoaded", (plugin) => plugin.onContentLoaded, {
2659
2682
  ...this.createContext(contentIndex),
2660
2683
  slug,
2661
2684
  markdown
@@ -2667,22 +2690,30 @@ var PluginRuntime = class {
2667
2690
  for (const plugin of this.plugins()) {
2668
2691
  const resolver = plugin.resolveContentLocations;
2669
2692
  if (!resolver) continue;
2670
- const resolved = await resolver(
2671
- this.createPluginContext(plugin, context)
2693
+ const pluginContext = this.createPluginContext(plugin, context);
2694
+ const resolved = await runPluginHook(
2695
+ plugin,
2696
+ "resolveContentLocations",
2697
+ pluginContext,
2698
+ resolver
2672
2699
  );
2673
2700
  locations.push(...resolved);
2674
2701
  }
2675
2702
  return locations;
2676
2703
  }
2677
2704
  async extendContentLocations(entries, locations, contentIndex) {
2678
- await this.runHook((plugin) => plugin.extendContentLocations, {
2679
- ...this.createContext(contentIndex),
2680
- entries,
2681
- locations
2682
- });
2705
+ await this.runHook(
2706
+ "extendContentLocations",
2707
+ (plugin) => plugin.extendContentLocations,
2708
+ {
2709
+ ...this.createContext(contentIndex),
2710
+ entries,
2711
+ locations
2712
+ }
2713
+ );
2683
2714
  }
2684
2715
  async runPostHook(hookName, slug, markdown, content, contentIndex) {
2685
- await this.runHook((plugin) => plugin[hookName], {
2716
+ await this.runHook(hookName, (plugin) => plugin[hookName], {
2686
2717
  ...this.createContext(contentIndex),
2687
2718
  slug,
2688
2719
  markdown,
@@ -2690,16 +2721,24 @@ var PluginRuntime = class {
2690
2721
  });
2691
2722
  }
2692
2723
  async runGraphHook(entries, contentIndex) {
2693
- await this.runHook((plugin) => plugin.extendContentGraph, {
2694
- ...this.createContext(contentIndex),
2695
- entries
2696
- });
2724
+ await this.runHook(
2725
+ "extendContentGraph",
2726
+ (plugin) => plugin.extendContentGraph,
2727
+ {
2728
+ ...this.createContext(contentIndex),
2729
+ entries
2730
+ }
2731
+ );
2697
2732
  }
2698
2733
  async runManifestCreated(manifest, contentIndex) {
2699
- await this.runHook((plugin) => plugin.onManifestCreated, {
2700
- ...this.createContext(contentIndex),
2701
- manifest
2702
- });
2734
+ await this.runHook(
2735
+ "onManifestCreated",
2736
+ (plugin) => plugin.onManifestCreated,
2737
+ {
2738
+ ...this.createContext(contentIndex),
2739
+ manifest
2740
+ }
2741
+ );
2703
2742
  }
2704
2743
  async runBuildEnd(manifest, contentIndex) {
2705
2744
  const context = {
@@ -2710,10 +2749,6 @@ var PluginRuntime = class {
2710
2749
  this.plugins(),
2711
2750
  (plugin) => this.createPluginContext(plugin, context)
2712
2751
  );
2713
- await this.runHook((plugin) => plugin.onBuildEnd, {
2714
- ...this.createContext(contentIndex),
2715
- manifest
2716
- });
2717
2752
  }
2718
2753
  enableBuildTime() {
2719
2754
  this.isBuildTime = true;
@@ -2795,63 +2830,74 @@ var PluginRuntime = class {
2795
2830
  return highest[0].page;
2796
2831
  }
2797
2832
  async getPagePaths(manifest, contentIndex) {
2798
- const paths = [];
2799
- const context = { ...this.createContext(contentIndex), manifest };
2800
- for (const plugin of this.plugins()) {
2801
- for (const pageType of plugin.pageTypes ?? []) {
2802
- const declared = pageType.paths;
2803
- if (!declared) continue;
2804
- const resolved = typeof declared === "function" ? await declared(this.createPluginContext(plugin, context)) : declared;
2805
- for (const rawPath of resolved) {
2806
- paths.push(normalizePageTypePath(rawPath, pageType));
2807
- }
2808
- }
2809
- }
2810
- return [...new Set(paths)];
2833
+ return [
2834
+ ...new Set(
2835
+ (await this.getPageDeclarations(manifest, contentIndex)).map(
2836
+ (declaration) => declaration.pathname
2837
+ )
2838
+ )
2839
+ ];
2811
2840
  }
2812
2841
  async getPageRoutes(manifest, contentIndex) {
2813
- const routes = [];
2814
- const context = { ...this.createContext(contentIndex), manifest };
2815
- for (const plugin of this.plugins()) {
2816
- for (const pageType of plugin.pageTypes ?? []) {
2817
- const declared = pageType.paths;
2818
- if (!declared) continue;
2819
- const paths = typeof declared === "function" ? await declared(this.createPluginContext(plugin, context)) : declared;
2820
- for (const pathname of paths) {
2821
- routes.push({
2822
- pathname: normalizePageTypePath(pathname, pageType),
2823
- pluginName: plugin.name,
2824
- pageType: pageType.id
2825
- });
2826
- }
2827
- }
2828
- }
2829
- 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
+ );
2830
2849
  }
2831
2850
  async getPageOutputs(manifest, contentIndex) {
2832
- 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 = [];
2833
2885
  const context = { ...this.createContext(contentIndex), manifest };
2834
2886
  for (const plugin of this.plugins()) {
2835
2887
  for (const pageType of plugin.pageTypes ?? []) {
2836
- const declared = pageType.paths;
2837
- if (!declared) continue;
2838
- const paths = typeof declared === "function" ? await declared(this.createPluginContext(plugin, context)) : declared;
2839
- for (const rawPath of paths) {
2840
- const pathname = normalizePageTypePath(rawPath, pageType);
2841
- const dependencyContext = { ...context, pathname };
2842
- const dependencies = pageType.outputDependencies ? typeof pageType.outputDependencies === "function" ? await pageType.outputDependencies(
2843
- this.createPluginContext(plugin, dependencyContext)
2844
- ) : pageType.outputDependencies : [{ type: "unknown" }];
2845
- outputs.push({
2846
- kind: "plugin-page",
2847
- path: pathname,
2848
- producer: `plugin:${plugin.name}:page:${pageType.id}`,
2849
- 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
2850
2896
  });
2851
2897
  }
2852
2898
  }
2853
2899
  }
2854
- return uniqueOutputs(outputs);
2900
+ return declarations;
2855
2901
  }
2856
2902
  getContentOutputDependencies() {
2857
2903
  return this.plugins().flatMap((plugin) => plugin.outputDependencies ?? []);
@@ -2862,9 +2908,13 @@ var PluginRuntime = class {
2862
2908
  const results = [];
2863
2909
  const context = this.createContext(contentIndex);
2864
2910
  for (const plugin of this.plugins()) {
2865
- const diagnostics = await plugin.addDiagnostics?.(
2866
- this.createPluginContext(plugin, context)
2867
- );
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;
2868
2918
  for (const diagnostic of diagnostics ?? []) {
2869
2919
  results.push({
2870
2920
  ...diagnostic,
@@ -2893,13 +2943,13 @@ var PluginRuntime = class {
2893
2943
  stage: "buildStart",
2894
2944
  run: () => runBuildStart(this.plugins(), createPluginContext)
2895
2945
  },
2896
- {
2897
- stage: "onBuildStart",
2898
- run: () => this.runHook((plugin) => plugin.onBuildStart, context)
2899
- },
2900
2946
  {
2901
2947
  stage: "onConfigResolved",
2902
- run: () => this.runHook((plugin) => plugin.onConfigResolved, context)
2948
+ run: () => this.runHook(
2949
+ "onConfigResolved",
2950
+ (plugin) => plugin.onConfigResolved,
2951
+ context
2952
+ )
2903
2953
  }
2904
2954
  ];
2905
2955
  for (const { stage, run } of stages) {
@@ -2944,18 +2994,12 @@ var PluginRuntime = class {
2944
2994
  this.pluginCaches.set(plugin.name, cache);
2945
2995
  return cache;
2946
2996
  }
2947
- async runHook(hook, context) {
2997
+ async runHook(hookName, hook, context) {
2948
2998
  for (const plugin of this.plugins()) {
2949
2999
  const pluginHook = hook(plugin);
2950
3000
  if (!pluginHook) continue;
2951
3001
  const pluginContext = this.createPluginContext(plugin, context);
2952
- await pluginContext.tracer.span(
2953
- "plugin.hook",
2954
- {
2955
- plugin: plugin.name
2956
- },
2957
- () => pluginHook(pluginContext)
2958
- );
3002
+ await runPluginHook(plugin, hookName, pluginContext, pluginHook);
2959
3003
  }
2960
3004
  }
2961
3005
  observability() {
@@ -3041,10 +3085,20 @@ function countSeverity(diagnostics, severity) {
3041
3085
  }
3042
3086
  function uniqueOutputs(outputs) {
3043
3087
  const byPath = /* @__PURE__ */ new Map();
3044
- for (const output of outputs) byPath.set(output.path, output);
3045
- return [...byPath.values()].sort(
3046
- (left, right) => left.path.localeCompare(right.path)
3047
- );
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));
3048
3102
  }
3049
3103
 
3050
3104
  // src/content/affected_content.ts
@@ -3459,8 +3513,8 @@ function collectDependencies2(manifestEntry, trackedDependencies) {
3459
3513
  kind: "file",
3460
3514
  id: asset.path.replace(/\\/g, "/")
3461
3515
  })),
3462
- ...trackedDependencies.filter(
3463
- (dependency) => dependency.kind === "content" || dependency.kind === "file"
3516
+ ...trackedDependencies.map(toBuildDependency).filter(
3517
+ (dependency) => dependency !== null
3464
3518
  ).map((dependency) => ({
3465
3519
  kind: dependency.kind,
3466
3520
  id: dependency.id.replace(/\\/g, "/")
@@ -3474,6 +3528,18 @@ function collectDependencies2(manifestEntry, trackedDependencies) {
3474
3528
  (left, right) => `${left.kind}:${left.id}`.localeCompare(`${right.kind}:${right.id}`)
3475
3529
  );
3476
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
+ }
3477
3543
  function collectLinkTargets(manifestEntry) {
3478
3544
  if (!manifestEntry) return [];
3479
3545
  return [
@@ -3663,8 +3729,17 @@ var FileSystemContentSource = class {
3663
3729
  contentDirectory;
3664
3730
  exclude;
3665
3731
  async scan() {
3732
+ return (await this.scanWithExclusions()).entries;
3733
+ }
3734
+ async scanWithExclusions() {
3735
+ const exclusions = [];
3666
3736
  try {
3667
- return await this.scanDirectory(this.contentDirectory, "");
3737
+ const entries = await this.scanDirectory(
3738
+ this.contentDirectory,
3739
+ "",
3740
+ exclusions
3741
+ );
3742
+ return { entries, exclusions };
3668
3743
  } catch (error) {
3669
3744
  if (isMissingDirectoryError(error, this.contentDirectory)) {
3670
3745
  throw missingContentDirectoryError(this.contentDirectory);
@@ -3680,7 +3755,7 @@ var FileSystemContentSource = class {
3680
3755
  }
3681
3756
  return await fs4.readFile(filePath);
3682
3757
  }
3683
- async scanDirectory(directory, parentPath2) {
3758
+ async scanDirectory(directory, parentPath2, exclusions) {
3684
3759
  const directoryEntries = await fs4.readdir(directory, {
3685
3760
  withFileTypes: true
3686
3761
  });
@@ -3691,9 +3766,16 @@ var FileSystemContentSource = class {
3691
3766
  parentPath2 ? `${parentPath2}/${directoryEntry.name}` : directoryEntry.name
3692
3767
  );
3693
3768
  const filePath = path4.join(directory, directoryEntry.name);
3694
- 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
+ }
3695
3775
  if (directoryEntry.isDirectory()) {
3696
- entries.push(...await this.scanDirectory(filePath, logicalPath));
3776
+ entries.push(
3777
+ ...await this.scanDirectory(filePath, logicalPath, exclusions)
3778
+ );
3697
3779
  continue;
3698
3780
  }
3699
3781
  if (!directoryEntry.isFile()) continue;
@@ -3708,9 +3790,6 @@ var FileSystemContentSource = class {
3708
3790
  }
3709
3791
  return entries;
3710
3792
  }
3711
- isExcluded(logicalPath) {
3712
- return isIgnoredContentPath(logicalPath, this.exclude);
3713
- }
3714
3793
  resolveFilePath(logicalPath) {
3715
3794
  const normalizedPath = normalizeLogicalPath(logicalPath);
3716
3795
  const filePath = path4.resolve(this.contentDirectory, normalizedPath);
@@ -3723,8 +3802,14 @@ var FileSystemContentSource = class {
3723
3802
  };
3724
3803
  function isIgnoredContentPath(logicalPath, exclude = []) {
3725
3804
  if (isExcluded(INTERNAL_CONTENT_IGNORE_PATTERNS, logicalPath)) return true;
3726
- if (isExcluded(exclude, logicalPath)) return true;
3727
- 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));
3728
3813
  }
3729
3814
  function normalizeLogicalPath(logicalPath) {
3730
3815
  const normalizedPath = normalizeContentPath(logicalPath);
@@ -3748,53 +3833,22 @@ function missingContentDirectoryError(directory) {
3748
3833
  return error;
3749
3834
  }
3750
3835
 
3751
- // src/content/content_links.ts
3752
- var WIKILINK_PATTERN = /(!)?\[\[([^\]|#^]+)(?:[#^][^\]|]+)?(?:\|[^\]]+)?\]\]/g;
3753
- function extractContentLinks(markdown, contentIndex) {
3754
- WIKILINK_PATTERN.lastIndex = 0;
3755
- const links = [];
3756
- for (let match = WIKILINK_PATTERN.exec(markdown); match !== null; match = WIKILINK_PATTERN.exec(markdown)) {
3757
- const rawTarget = match[2]?.trim();
3758
- if (!rawTarget) continue;
3759
- const resolved = contentIndex.get(rawTarget.toLowerCase()) ?? null;
3760
- links.push({
3761
- raw: rawTarget,
3762
- slug: resolved,
3763
- kind: resolved ? linkKind(resolved) : "unresolved",
3764
- embed: match[1] === "!"
3765
- });
3766
- }
3767
- return links;
3768
- }
3769
- function linkKind(value) {
3770
- if (isImagePath(value)) return "image";
3771
- if (isAttachmentPath(value)) return "attachment";
3772
- return "note";
3773
- }
3774
-
3775
3836
  // src/content/content_stable_id.ts
3776
3837
  var MAX_STABLE_CONTENT_ID_LENGTH = 160;
3777
3838
  function resolveContentStableId(frontmatter) {
3778
- const id = readOptionalStableId(frontmatter.id, "id");
3779
- const uid = readOptionalStableId(frontmatter.uid, "uid");
3780
- if (id !== void 0 && uid !== void 0 && id !== uid) {
3781
- throw new Error(
3782
- 'Content frontmatter fields "id" and "uid" must have the same value.'
3783
- );
3784
- }
3785
- return id ?? uid;
3839
+ return readOptionalStableId(frontmatter.id);
3786
3840
  }
3787
- function readOptionalStableId(value, field) {
3841
+ function readOptionalStableId(value) {
3788
3842
  if (value === void 0) return void 0;
3789
3843
  if (typeof value !== "string") {
3790
- throw new Error(`Content frontmatter field "${field}" must be a string.`);
3844
+ throw new Error(`Content frontmatter field "id" must be a string.`);
3791
3845
  }
3792
3846
  if (value.length === 0 || value.length > MAX_STABLE_CONTENT_ID_LENGTH || value.trim() !== value || [...value].some((character) => {
3793
3847
  const code = character.codePointAt(0) ?? 0;
3794
3848
  return code <= 31 || code === 127;
3795
3849
  })) {
3796
3850
  throw new Error(
3797
- `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.`
3798
3852
  );
3799
3853
  }
3800
3854
  return value;
@@ -4101,8 +4155,20 @@ function linkTargets(entry) {
4101
4155
  ).map((link) => link.slug);
4102
4156
  }
4103
4157
  function addOutput(outputs, output) {
4158
+ const existing = outputs.get(output.path);
4159
+ if (existing) {
4160
+ assertNoGeneratedOutputCollision(existing, output);
4161
+ }
4104
4162
  outputs.set(output.path, output);
4105
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
+ }
4106
4172
  function normalizePublicPath(pathname) {
4107
4173
  const path5 = `/${pathname.split("/").filter(Boolean).join("/")}`;
4108
4174
  return path5 === "/" ? path5 : path5.replace(/\/$/, "");
@@ -4182,9 +4248,14 @@ var CONTENT_PROCESSING_CONCURRENCY = 16;
4182
4248
  var ContentManager = class {
4183
4249
  constructor(content, exclude = [], pipelineOptions = {}) {
4184
4250
  this.pipelineOptions = pipelineOptions;
4251
+ const config = pipelineOptions.config ? {
4252
+ ...pipelineOptions.config,
4253
+ plugins: pipelineOptions.plugins ?? pipelineOptions.config.plugins
4254
+ } : void 0;
4185
4255
  this.pipelineOptions = {
4186
4256
  ...pipelineOptions,
4187
- plugins: pipelineOptions.plugins ?? pipelineOptions.config?.plugins,
4257
+ config,
4258
+ plugins: pipelineOptions.plugins ?? config?.plugins,
4188
4259
  isRoutable: (slug) => this.isRoutable(slug)
4189
4260
  };
4190
4261
  this.publishingBuildTime = resolvePublishingBuildTime(
@@ -5077,6 +5148,7 @@ export {
5077
5148
  buildOutputInventory,
5078
5149
  calculateReadingTime,
5079
5150
  collectConfigDeprecationDiagnostics,
5151
+ computePipelineFingerprint,
5080
5152
  createClientEntry,
5081
5153
  createContentGraph,
5082
5154
  createDeprecationDiagnostic,
@@ -5095,6 +5167,7 @@ export {
5095
5167
  fingerprintContentEntries,
5096
5168
  formatDeprecationMessage,
5097
5169
  getContentSourceEntry,
5170
+ getEntryLanguage,
5098
5171
  getExtension,
5099
5172
  getResolvedPluginMetadata,
5100
5173
  groupContentEntries,
@@ -5104,11 +5177,13 @@ export {
5104
5177
  isIgnoredContentPath,
5105
5178
  isImagePath,
5106
5179
  isMarkdownPath,
5180
+ isPersistentlyCacheable,
5107
5181
  isPublishable,
5108
5182
  isPublished,
5109
5183
  layoutForceGraph,
5110
5184
  layoutRadialGraph,
5111
5185
  loadContentBuildState,
5186
+ matchContentExcludePattern,
5112
5187
  normalizeContentAssetOutputPath,
5113
5188
  normalizeContentPath,
5114
5189
  normalizeGeneratedOutputPath,
@@ -5131,6 +5206,7 @@ export {
5131
5206
  resolvePublicFolderLocation,
5132
5207
  resolvePublishingBuildTime,
5133
5208
  resolvePublishingState,
5209
+ selectEntriesByLanguage,
5134
5210
  serializePublicClientConfig,
5135
5211
  stableStringify,
5136
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;
@@ -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 = {
@@ -71,6 +71,7 @@ export type ContentRedirect = {
71
71
  export type ContentPublicLocation = {
72
72
  slug: string;
73
73
  permalink: string;
74
+ language?: string;
74
75
  redirects?: readonly ContentRedirect[];
75
76
  metadata?: Readonly<Record<string, string>>;
76
77
  };
@@ -90,9 +91,10 @@ export type ContentLocationInput = {
90
91
  export type ContentManifest = {
91
92
  entries: ContentManifestEntry[];
92
93
  /**
93
- * Entries that pass the configured publish strategy. `entries` keeps every
94
- * scanned note for backward compatibility; publishing plugins should read
95
- * 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.
96
98
  */
97
99
  publicEntries: ContentManifestEntry[];
98
100
  /** Entries that may appear on discovery surfaces such as navigation, search, feeds, taxonomy, and public graphs. */
@@ -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;
@@ -1,5 +1,4 @@
1
1
  import type { ContentSource } from "../content/content_source.js";
2
- import type { NavigationConfig } from "./navigation_config.js";
3
2
  import type { RiebeckitePlugin } from "./plugin.js";
4
3
  import type { PublishStrategy } from "./publish_strategy.js";
5
4
  import type { SiteConfig } from "./site_config.js";
@@ -10,8 +9,13 @@ export type ResolvedRiebeckiteConfig = {
10
9
  * relative to their application root.
11
10
  */
12
11
  buildDirectory?: string;
13
- site: Required<SiteConfig>;
14
- navigation: Required<NavigationConfig>;
12
+ site: Omit<Required<SiteConfig>, "feed"> & {
13
+ feed: {
14
+ title: string;
15
+ description: string;
16
+ language: string;
17
+ };
18
+ };
15
19
  content: {
16
20
  directory: string;
17
21
  source?: ContentSource;
@@ -20,11 +24,6 @@ export type ResolvedRiebeckiteConfig = {
20
24
  publishStrategy: PublishStrategy;
21
25
  };
22
26
  };
23
- markdown: {
24
- syntaxHighlight: {
25
- theme: string;
26
- };
27
- };
28
27
  theme: Required<Omit<ThemeConfig, "tokens">> & {
29
28
  tokens: NonNullable<ThemeConfig["tokens"]>;
30
29
  styles: ThemeStyle[];
@@ -1,12 +1,10 @@
1
1
  import type { ContentSource } from "../content/content_source.js";
2
- import type { NavigationConfig } from "./navigation_config.js";
3
2
  import type { PluginInput } from "./plugin.js";
4
3
  import type { PublishStrategy } from "./publish_strategy.js";
5
4
  import type { SiteConfig } from "./site_config.js";
6
5
  import type { ThemeInput } from "./theme_config.js";
7
6
  export interface RiebeckiteConfig {
8
7
  site: SiteConfig;
9
- navigation?: NavigationConfig;
10
8
  content?: {
11
9
  directory?: string;
12
10
  source?: ContentSource;
@@ -15,11 +13,6 @@ export interface RiebeckiteConfig {
15
13
  publishStrategy?: PublishStrategy;
16
14
  };
17
15
  };
18
- markdown?: {
19
- syntaxHighlight?: {
20
- theme?: string;
21
- };
22
- };
23
16
  theme?: ThemeInput;
24
17
  plugins?: PluginInput[];
25
18
  cache?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riebeckite/core",
3
- "version": "0.0.16",
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.16"
52
+ "@riebeckite/test": "0.0.17"
53
53
  },
54
54
  "repository": {
55
55
  "type": "git",
@@ -1,10 +0,0 @@
1
- export interface NavigationItem {
2
- label: string;
3
- href: string;
4
- children?: readonly NavigationItem[];
5
- external?: boolean;
6
- }
7
- export interface NavigationConfig {
8
- header?: readonly NavigationItem[];
9
- footer?: readonly NavigationItem[];
10
- }