@riebeckite/core 0.0.12 → 0.0.14

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
@@ -1,94 +1,94 @@
1
- # @riebeckite/core
2
-
3
- The portable framework contract for Riebeckite: configuration, content sources,
4
- the content manager, the Markdown/HTML pipeline, plugin and theme contracts,
5
- diagnostics, and observability.
6
-
7
- [日本語](./README_ja.md)
8
-
9
- ## Overview
10
-
11
- `@riebeckite/core` owns content loading and interpretation and the contracts
12
- that extensions build against. It is intentionally free of HonoX, Vite, and any
13
- specific plugin, theme, or application: those depend on Core, never the reverse.
14
-
15
- A site configures Core through `defineConfig`, and a framework integration such
16
- as [`@riebeckite/honox`](../integrations/honox/README.md) drives the build.
17
- Plugins and themes are plain objects created by `definePlugin` and `defineTheme`.
18
-
19
- ## Installation
20
-
21
- ```sh
22
- pnpm add @riebeckite/core
23
- ```
24
-
25
- Core is also installed transitively by `@riebeckite/honox` and by the
26
- `@riebeckite/plugin-*` and `@riebeckite/theme-*` packages.
27
-
28
- ## Usage
29
-
30
- ```ts
31
- import { defineConfig } from "@riebeckite/core";
32
-
33
- export default defineConfig({
34
- site: { title: "My notes", baseUrl: "https://example.com" },
35
- content: { directory: "../../content" },
36
- });
37
- ```
38
-
39
- The content directory is resolved relative to the application root. See
40
- [Configuration](../../docs/en/configuration.md) for the full field list and the
41
- `projectRoot` / `appRoot` / `configRoot` / `contentRoot` resolution rules.
42
-
43
- ## Public API
44
-
45
- ### Configuration
46
-
47
- - `defineConfig`, `resolveConfig`, `resolveConfigModule`, `isPublished`,
48
- `isExcluded`
49
- - `ConfigValidationError`
50
-
51
- ### Content
52
-
53
- - `ContentManager` — interpretation, manifest, graph, plugin orchestration, and
54
- public-location resolution
55
- - `ContentSource`, `FileSystemContentSource`, `readContentSourceEntry`,
56
- `getContentSourceEntry`
57
- - `queryContentEntries` with the `ContentQuery*` filter and sort types
58
- - `createContentGraph`, `readOnlyContentGraph`, `buildGraphEdges`,
59
- `layoutRadialGraph`
60
- - `resolveDefaultContentLocation`, `attachmentUrl`, and the content path and
61
- extension helpers
62
- - `readContentBuildStateStatus`, `resolveContentBuildStatePath`
63
-
64
- ### Pipeline
65
-
66
- - `Pipeline`, `PipelineOptions`
67
-
68
- ### Plugins
69
-
70
- - `definePlugin`, `resolvePlugins`, `getResolvedPluginMetadata`,
71
- `PluginDependencyError`
72
- - `defineEndpoint`, `createClientEntry`, `createStyleAsset`
73
- - Plugin context, asset, endpoint, diagnostic, SEO, and pipeline types under
74
- `Plugin*`
75
-
76
- ### Themes and contracts
77
-
78
- - `defineTheme` and the `Theme*` types
79
- - `RiebeckiteConfig`, `ResolvedRiebeckiteConfig`, `SiteConfig`,
80
- `PostContent`, `PostFrontmatter`, `PublishStrategy`
81
-
82
- ### Utilities and observability
83
-
84
- - `uniqueStrings`, `escapeHtml`, `escapeHtmlAttribute`, `normalizeTag`,
85
- `calculateReadingTime`, `stripHtml`
86
- - `ConsoleLogger`, `NoopLogger`, `NoopTracer`, `SinkTracer`,
87
- `CompositeTraceSink`
88
-
89
- ## See also
90
-
91
- - [Framework Reference](../../docs/en/framework-reference.md)
92
- - [Content System](../../docs/en/content-system.md)
93
- - [Plugin System](../../docs/en/plugin-system.md) / [Theme System](../../docs/en/theme-system.md)
94
- - [Architecture](../../docs/en/architecture.md)
1
+ # @riebeckite/core
2
+
3
+ The portable framework contract for Riebeckite: configuration, content sources,
4
+ the content manager, the Markdown/HTML pipeline, plugin and theme contracts,
5
+ diagnostics, and observability.
6
+
7
+ [日本語](./README_ja.md)
8
+
9
+ ## Overview
10
+
11
+ `@riebeckite/core` owns content loading and interpretation and the contracts
12
+ that extensions build against. It is intentionally free of HonoX, Vite, and any
13
+ specific plugin, theme, or application: those depend on Core, never the reverse.
14
+
15
+ A site configures Core through `defineConfig`, and a framework integration such
16
+ as [`@riebeckite/honox`](../integrations/honox/README.md) drives the build.
17
+ Plugins and themes are plain objects created by `definePlugin` and `defineTheme`.
18
+
19
+ ## Installation
20
+
21
+ ```sh
22
+ pnpm add @riebeckite/core
23
+ ```
24
+
25
+ Core is also installed transitively by `@riebeckite/honox` and by the
26
+ `@riebeckite/plugin-*` and `@riebeckite/theme-*` packages.
27
+
28
+ ## Usage
29
+
30
+ ```ts
31
+ import { defineConfig } from "@riebeckite/core";
32
+
33
+ export default defineConfig({
34
+ site: { title: "My notes", baseUrl: "https://example.com" },
35
+ content: { directory: "../../content" },
36
+ });
37
+ ```
38
+
39
+ The content directory is resolved relative to the application root. See
40
+ [Configuration](../../docs/en/reference/configuration.md) for the full field list and the
41
+ `projectRoot` / `appRoot` / `configRoot` / `contentRoot` resolution rules.
42
+
43
+ ## Public API
44
+
45
+ ### Configuration
46
+
47
+ - `defineConfig`, `resolveConfig`, `resolveConfigModule`, `isPublished`,
48
+ `isExcluded`
49
+ - `ConfigValidationError`
50
+
51
+ ### Content
52
+
53
+ - `ContentManager` — interpretation, manifest, graph, plugin orchestration, and
54
+ public-location resolution
55
+ - `ContentSource`, `FileSystemContentSource`, `readContentSourceEntry`,
56
+ `getContentSourceEntry`
57
+ - `queryContentEntries` with the `ContentQuery*` filter and sort types
58
+ - `createContentGraph`, `readOnlyContentGraph`, `buildGraphEdges`,
59
+ `layoutRadialGraph`
60
+ - `resolveDefaultContentLocation`, `attachmentUrl`, and the content path and
61
+ extension helpers
62
+ - `readContentBuildStateStatus`, `resolveContentBuildStatePath`
63
+
64
+ ### Pipeline
65
+
66
+ - `Pipeline`, `PipelineOptions`
67
+
68
+ ### Plugins
69
+
70
+ - `definePlugin`, `resolvePlugins`, `getResolvedPluginMetadata`,
71
+ `PluginDependencyError`
72
+ - `defineEndpoint`, `createClientEntry`, `createStyleAsset`
73
+ - Plugin context, asset, endpoint, diagnostic, SEO, and pipeline types under
74
+ `Plugin*`
75
+
76
+ ### Themes and contracts
77
+
78
+ - `defineTheme` and the `Theme*` types
79
+ - `RiebeckiteConfig`, `ResolvedRiebeckiteConfig`, `SiteConfig`,
80
+ `PostContent`, `PostFrontmatter`, `PublishStrategy`
81
+
82
+ ### Utilities and observability
83
+
84
+ - `uniqueStrings`, `escapeHtml`, `escapeHtmlAttribute`, `normalizeTag`,
85
+ `calculateReadingTime`, `stripHtml`
86
+ - `ConsoleLogger`, `NoopLogger`, `NoopTracer`, `SinkTracer`,
87
+ `CompositeTraceSink`
88
+
89
+ ## See also
90
+
91
+ - [Framework Reference](../../docs/en/reference/README.md)
92
+ - [Content System](../../docs/en/framework/content-system.md)
93
+ - [Plugin System](../../docs/en/reference/plugin-api.md) / [Theme System](../../docs/en/reference/theme-api.md)
94
+ - [Architecture](../../docs/en/framework/architecture.md)
package/README_ja.md CHANGED
@@ -1,75 +1,75 @@
1
- # @riebeckite/core
2
-
3
- Riebeckite の移植可能なフレームワーク契約です。設定、コンテンツソース、ContentManager、Markdown/HTML パイプライン、Plugin と Theme の契約、診断、可観測性を担います。
4
-
5
- [English](./README.md)
6
-
7
- ## 概要
8
-
9
- `@riebeckite/core` はコンテンツの読み込みと解釈、および拡張が依存する契約を所有します。HonoX や Vite、特定の Plugin・Theme・アプリケーションには依存しません。依存の向きは常に Core へ向かい、その逆には向かいません。
10
-
11
- サイトは `defineConfig` で Core を設定し、[`@riebeckite/honox`](../integrations/honox/README_ja.md) のような integration がビルドを実行します。Plugin と Theme は `definePlugin`、`defineTheme` が返すただのオブジェクトです。
12
-
13
- ## インストール
14
-
15
- ```sh
16
- pnpm add @riebeckite/core
17
- ```
18
-
19
- `@riebeckite/honox`、`@riebeckite/plugin-*`、`@riebeckite/theme-*` をインストールすると、依存として一緒に入ります。
20
-
21
- ## 使い方
22
-
23
- ```ts
24
- import { defineConfig } from "@riebeckite/core";
25
-
26
- export default defineConfig({
27
- site: { title: "My notes", baseUrl: "https://example.com" },
28
- content: { directory: "../../content" },
29
- });
30
- ```
31
-
32
- コンテンツディレクトリはアプリケーションルート基準で解決します。設定項目の全体と `projectRoot` / `appRoot` / `configRoot` / `contentRoot` の解決規則は [Configuration](../../docs/ja/configuration.md) を参照してください。
33
-
34
- ## 公開 API
35
-
36
- ### 設定
37
-
38
- - `defineConfig`、`resolveConfig`、`resolveConfigModule`、`isPublished`、`isExcluded`
39
- - `ConfigValidationError`
40
-
41
- ### コンテンツ
42
-
43
- - `ContentManager` — 解釈、マニフェスト、グラフ、Plugin の実行、公開 URL の解決
44
- - `ContentSource`、`FileSystemContentSource`、`readContentSourceEntry`、`getContentSourceEntry`
45
- - `queryContentEntries` と `ContentQuery*` のフィルタ・ソート型
46
- - `createContentGraph`、`readOnlyContentGraph`、`buildGraphEdges`、`layoutRadialGraph`
47
- - `resolveDefaultContentLocation`、`attachmentUrl`、コンテンツパスと拡張子のヘルパー
48
- - `readContentBuildStateStatus`、`resolveContentBuildStatePath`
49
-
50
- ### パイプライン
51
-
52
- - `Pipeline`、`PipelineOptions`
53
-
54
- ### Plugin
55
-
56
- - `definePlugin`、`resolvePlugins`、`getResolvedPluginMetadata`、`PluginDependencyError`
57
- - `defineEndpoint`、`createClientEntry`、`createStyleAsset`
58
- - `Plugin*` のコンテキスト、アセット、エンドポイント、診断、SEO、パイプラインの型
59
-
60
- ### Theme と契約
61
-
62
- - `defineTheme` と `Theme*` の型
63
- - `RiebeckiteConfig`、`ResolvedRiebeckiteConfig`、`SiteConfig`、`PostContent`、`PostFrontmatter`、`PublishStrategy`
64
-
65
- ### ユーティリティと可観測性
66
-
67
- - `uniqueStrings`、`escapeHtml`、`escapeHtmlAttribute`、`normalizeTag`、`calculateReadingTime`、`stripHtml`
68
- - `ConsoleLogger`、`NoopLogger`、`NoopTracer`、`SinkTracer`、`CompositeTraceSink`
69
-
70
- ## 関連資料
71
-
72
- - [Framework Reference](../../docs/ja/framework-reference.md)
73
- - [Content System](../../docs/ja/content-system.md)
74
- - [Plugin System](../../docs/ja/plugin-system.md) / [Theme System](../../docs/ja/theme-system.md)
75
- - [Architecture](../../docs/ja/architecture.md)
1
+ # @riebeckite/core
2
+
3
+ Riebeckite の移植可能なフレームワーク契約です。設定、コンテンツソース、ContentManager、Markdown/HTML パイプライン、Plugin と Theme の契約、診断、可観測性を担います。
4
+
5
+ [English](./README.md)
6
+
7
+ ## 概要
8
+
9
+ `@riebeckite/core` はコンテンツの読み込みと解釈、および拡張が依存する契約を所有します。HonoX や Vite、特定の Plugin・Theme・アプリケーションには依存しません。依存の向きは常に Core へ向かい、その逆には向かいません。
10
+
11
+ サイトは `defineConfig` で Core を設定し、[`@riebeckite/honox`](../integrations/honox/README_ja.md) のような integration がビルドを実行します。Plugin と Theme は `definePlugin`、`defineTheme` が返すただのオブジェクトです。
12
+
13
+ ## インストール
14
+
15
+ ```sh
16
+ pnpm add @riebeckite/core
17
+ ```
18
+
19
+ `@riebeckite/honox`、`@riebeckite/plugin-*`、`@riebeckite/theme-*` をインストールすると、依存として一緒に入ります。
20
+
21
+ ## 使い方
22
+
23
+ ```ts
24
+ import { defineConfig } from "@riebeckite/core";
25
+
26
+ export default defineConfig({
27
+ site: { title: "My notes", baseUrl: "https://example.com" },
28
+ content: { directory: "../../content" },
29
+ });
30
+ ```
31
+
32
+ コンテンツディレクトリはアプリケーションルート基準で解決します。設定項目の全体と `projectRoot` / `appRoot` / `configRoot` / `contentRoot` の解決規則は [Configuration](../../docs/ja/reference/configuration.md) を参照してください。
33
+
34
+ ## 公開 API
35
+
36
+ ### 設定
37
+
38
+ - `defineConfig`、`resolveConfig`、`resolveConfigModule`、`isPublished`、`isExcluded`
39
+ - `ConfigValidationError`
40
+
41
+ ### コンテンツ
42
+
43
+ - `ContentManager` — 解釈、マニフェスト、グラフ、Plugin の実行、公開 URL の解決
44
+ - `ContentSource`、`FileSystemContentSource`、`readContentSourceEntry`、`getContentSourceEntry`
45
+ - `queryContentEntries` と `ContentQuery*` のフィルタ・ソート型
46
+ - `createContentGraph`、`readOnlyContentGraph`、`buildGraphEdges`、`layoutRadialGraph`
47
+ - `resolveDefaultContentLocation`、`attachmentUrl`、コンテンツパスと拡張子のヘルパー
48
+ - `readContentBuildStateStatus`、`resolveContentBuildStatePath`
49
+
50
+ ### パイプライン
51
+
52
+ - `Pipeline`、`PipelineOptions`
53
+
54
+ ### Plugin
55
+
56
+ - `definePlugin`、`resolvePlugins`、`getResolvedPluginMetadata`、`PluginDependencyError`
57
+ - `defineEndpoint`、`createClientEntry`、`createStyleAsset`
58
+ - `Plugin*` のコンテキスト、アセット、エンドポイント、診断、SEO、パイプラインの型
59
+
60
+ ### Theme と契約
61
+
62
+ - `defineTheme` と `Theme*` の型
63
+ - `RiebeckiteConfig`、`ResolvedRiebeckiteConfig`、`SiteConfig`、`PostContent`、`PostFrontmatter`、`PublishStrategy`
64
+
65
+ ### ユーティリティと可観測性
66
+
67
+ - `uniqueStrings`、`escapeHtml`、`escapeHtmlAttribute`、`normalizeTag`、`calculateReadingTime`、`stripHtml`
68
+ - `ConsoleLogger`、`NoopLogger`、`NoopTracer`、`SinkTracer`、`CompositeTraceSink`
69
+
70
+ ## 関連資料
71
+
72
+ - [Framework Reference](../../docs/ja/reference/README.md)
73
+ - [Content System](../../docs/ja/framework/content-system.md)
74
+ - [Plugin System](../../docs/ja/reference/plugin-api.md) / [Theme System](../../docs/ja/reference/theme-api.md)
75
+ - [Architecture](../../docs/ja/framework/architecture.md)
@@ -0,0 +1,2 @@
1
+ export type { GraphEdge, GraphLayoutNode, LinkableGraphNode, RadialGraphLayoutOptions, } from "./src/content/graph_layout.js";
2
+ export { buildGraphEdges, layoutRadialGraph, } from "./src/content/graph_layout.js";
package/dist/client.js ADDED
@@ -0,0 +1,43 @@
1
+ // src/content/graph_layout.ts
2
+ function buildGraphEdges(nodes, visibleSlugs = new Set(nodes.map((node) => node.slug))) {
3
+ const edges = /* @__PURE__ */ new Map();
4
+ for (const node of nodes) {
5
+ for (const target of node.outgoing) {
6
+ if (!visibleSlugs.has(node.slug) || !visibleSlugs.has(target)) continue;
7
+ const key = `${node.slug}\0${target}`;
8
+ edges.set(key, { source: node.slug, target });
9
+ }
10
+ }
11
+ return Array.from(edges.values());
12
+ }
13
+ function layoutRadialGraph(nodes, options) {
14
+ const layout = /* @__PURE__ */ new Map();
15
+ const centerX = options.width / 2;
16
+ const centerY = options.height / 2;
17
+ const radius = Math.min(options.width, options.height) * (options.radiusRatio ?? 0.36);
18
+ const minNodeRadius = options.minNodeRadius ?? 8;
19
+ const maxNodeRadius = options.maxNodeRadius ?? 14;
20
+ const selectedIndex = Math.max(
21
+ nodes.findIndex((node) => node.slug === options.centerSlug),
22
+ 0
23
+ );
24
+ nodes.forEach((node, index) => {
25
+ const shiftedIndex = (index - selectedIndex + nodes.length) % nodes.length;
26
+ const angle = shiftedIndex / Math.max(nodes.length, 1) * Math.PI * 2;
27
+ const linkCount = node.outgoing.length + node.backlinks.length;
28
+ const isCenter = node.slug === options.centerSlug;
29
+ layout.set(node.slug, {
30
+ x: isCenter ? centerX : centerX + Math.cos(angle) * radius,
31
+ y: isCenter ? centerY : centerY + Math.sin(angle) * radius,
32
+ radius: clamp(7 + linkCount, minNodeRadius, maxNodeRadius)
33
+ });
34
+ });
35
+ return layout;
36
+ }
37
+ function clamp(value, min, max) {
38
+ return Math.min(Math.max(value, min), max);
39
+ }
40
+ export {
41
+ buildGraphEdges,
42
+ layoutRadialGraph
43
+ };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { defineConfig, isExcluded, isPublished, resolveConfig, resolveConfigModule, } from "./src/config.js";
2
+ export { type ConfigDeprecationNotice, collectConfigDeprecationDiagnostics, } from "./src/config_deprecations.js";
2
3
  export { ConfigValidationError } from "./src/config_validation.js";
3
4
  export { ATTACHMENTS_BASE_PATH, attachmentUrl, getExtension, isAttachmentPath, isImagePath, isMarkdownPath, normalizeContentPath, } from "./src/content/attachment.js";
4
5
  export type { ContentBuildState, FingerprintedContentEntry, } from "./src/content/content_build_state.js";
@@ -23,6 +24,7 @@ export type { GraphEdge, GraphLayoutNode, LinkableGraphNode, RadialGraphLayoutOp
23
24
  export { buildGraphEdges, layoutRadialGraph, } from "./src/content/graph_layout.js";
24
25
  export { IMAGE_EXTENSIONS } from "./src/content/image_extensions.js";
25
26
  export { readOnlyContentGraph } from "./src/content/read_only_content_graph.js";
27
+ export { createDeprecationDiagnostic, type DeprecationKind, type DeprecationNotice, formatDeprecationMessage, } from "./src/deprecation.js";
26
28
  export type { LogContext, Logger, LogLevel, Observability, ObservabilityValue, TraceAttributes, TraceEvent, Tracer, TraceSink, TraceSpan, } from "./src/observability.js";
27
29
  export { CompositeTraceSink, ConsoleLogger, NoopLogger, NoopTracer, SinkTracer, } from "./src/observability.js";
28
30
  export type { PipelineOptions } from "./src/pipeline.js";
@@ -47,6 +49,7 @@ export type { PluginDiagnostic, PluginDiagnosticLevel, } from "./src/types/plugi
47
49
  export type { PluginEndpoint, PluginEndpointContext, PluginEndpointMethod, PluginEndpointOptions, PluginEndpointRequest, PluginEndpointResponse, } from "./src/types/plugin_endpoint.js";
48
50
  export { defineEndpoint } from "./src/types/plugin_endpoint.js";
49
51
  export type { PluginHeadTag } from "./src/types/plugin_head.js";
52
+ export type { PluginPage, PluginPageContext, PluginPageType, ResolvedPluginPage, } from "./src/types/plugin_page.js";
50
53
  export type { HtmlPipeline, MarkdownEmbedFragment, MarkdownExecutionContext, MarkdownPipeline, MarkdownPipelineContext, PipelinePlugin, } from "./src/types/plugin_pipeline.js";
51
54
  export type { PluginSeoExtension, RenderableFeedEntry, SeoMetadata, WebsiteSeoInput, } from "./src/types/plugin_seo.js";
52
55
  export type { PostContent, PostFrontmatter } from "./src/types/post_content.js";
package/dist/index.js CHANGED
@@ -693,6 +693,46 @@ function globToRegExp(glob) {
693
693
  return new RegExp(`^${re}$`);
694
694
  }
695
695
 
696
+ // src/deprecation.ts
697
+ function createDeprecationDiagnostic(notice) {
698
+ return {
699
+ code: `deprecated-${notice.kind}`,
700
+ severity: "warning",
701
+ message: formatDeprecationMessage(notice),
702
+ target: notice.target,
703
+ suggestion: notice.replacement ? `Use ${notice.replacement}. ${notice.action}` : notice.action,
704
+ meta: { deprecation: notice }
705
+ };
706
+ }
707
+ function formatDeprecationMessage(notice) {
708
+ const lines = [
709
+ `Deprecated: ${notice.target}`,
710
+ `Deprecated since: ${notice.deprecatedSince}`
711
+ ];
712
+ if (notice.replacement) lines.push(`Use: ${notice.replacement}`);
713
+ lines.push(`Migration: ${notice.action}`);
714
+ if (notice.documentationUrl) lines.push(`See: ${notice.documentationUrl}`);
715
+ if (notice.removedIn) lines.push(`Removal planned: ${notice.removedIn}`);
716
+ return lines.join("\n");
717
+ }
718
+
719
+ // src/config_deprecations.ts
720
+ var configDeprecations = [];
721
+ function collectConfigDeprecationDiagnostics(config, notices = configDeprecations) {
722
+ return notices.filter((notice) => hasConfigPath(config, notice.path)).map(createDeprecationDiagnostic);
723
+ }
724
+ function hasConfigPath(config, path4) {
725
+ let value = config;
726
+ for (const segment of path4.split(".")) {
727
+ if (!isRecord2(value) || !(segment in value)) return false;
728
+ value = value[segment];
729
+ }
730
+ return value !== void 0;
731
+ }
732
+ function isRecord2(value) {
733
+ return typeof value === "object" && value !== null && !Array.isArray(value);
734
+ }
735
+
696
736
  // src/content/image_extensions.ts
697
737
  var IMAGE_EXTENSIONS = [
698
738
  "png",
@@ -770,7 +810,7 @@ async function readContentBuildStateStatus(statePath) {
770
810
  }
771
811
  throw error;
772
812
  }
773
- if (!isRecord2(parsed)) {
813
+ if (!isRecord3(parsed)) {
774
814
  return { kind: "invalid", path: statePath, reason: "invalid-shape" };
775
815
  }
776
816
  if (parsed.version !== CONTENT_BUILD_STATE_VERSION) {
@@ -810,19 +850,19 @@ function resolveContentBuildStatePath(config, contentDirectory) {
810
850
  );
811
851
  }
812
852
  function isContentBuildState(value) {
813
- return isRecord2(value) && value.version === CONTENT_BUILD_STATE_VERSION && isContentBuildStateShape(value);
853
+ return isRecord3(value) && value.version === CONTENT_BUILD_STATE_VERSION && isContentBuildStateShape(value);
814
854
  }
815
855
  function isContentBuildStateShape(value) {
816
- if (!isRecord2(value.entries) || !isRecord2(value.contentIndex)) return false;
856
+ if (!isRecord3(value.entries) || !isRecord3(value.contentIndex)) return false;
817
857
  return Object.values(value.entries).every(
818
- (entry) => isRecord2(entry) && typeof entry.fingerprint === "string" && entry.fingerprint.length > 0 && Array.isArray(entry.dependencies) && entry.dependencies.every(
858
+ (entry) => isRecord3(entry) && typeof entry.fingerprint === "string" && entry.fingerprint.length > 0 && Array.isArray(entry.dependencies) && entry.dependencies.every(
819
859
  (dependency) => typeof dependency === "string"
820
860
  )
821
861
  ) && Object.values(value.contentIndex).every(
822
862
  (entry) => typeof entry === "string"
823
863
  );
824
864
  }
825
- function isRecord2(value) {
865
+ function isRecord3(value) {
826
866
  return typeof value === "object" && value !== null && !Array.isArray(value);
827
867
  }
828
868
  function isNotFoundError(error) {
@@ -1588,6 +1628,7 @@ var Pipeline = class {
1588
1628
  markdownPipelineContext
1589
1629
  );
1590
1630
  }
1631
+ this.use(processor, normalizeMarkdownLinks, markdownPipelineContext);
1591
1632
  this.use(processor, remarkRehype, { allowDangerousHtml: true });
1592
1633
  this.use(processor, rehypeRaw);
1593
1634
  for (const plugin of plugins) {
@@ -1701,6 +1742,81 @@ function selectBlockFragment(markdown, blockId) {
1701
1742
  const line = lines.find((currentLine) => blockIdRe.test(currentLine));
1702
1743
  return line?.replace(blockIdRe, "").trimEnd() || null;
1703
1744
  }
1745
+ function normalizeMarkdownLinks(context) {
1746
+ return (tree) => {
1747
+ visitMarkdownLinkNodes(tree, (node) => {
1748
+ const normalizedUrl = normalizeMarkdownLinkUrl(node.url, context);
1749
+ if (normalizedUrl) node.url = normalizedUrl;
1750
+ });
1751
+ };
1752
+ }
1753
+ function visitMarkdownLinkNodes(node, visitor) {
1754
+ const linkNode = node;
1755
+ if ((node.type === "link" || node.type === "definition") && typeof linkNode.url === "string") {
1756
+ visitor(linkNode);
1757
+ }
1758
+ for (const child of linkNode.children ?? []) {
1759
+ visitMarkdownLinkNodes(child, visitor);
1760
+ }
1761
+ }
1762
+ function normalizeMarkdownLinkUrl(url, context) {
1763
+ if (!context.sourceSlug) return null;
1764
+ if (!isMarkdownContentLink(url)) return null;
1765
+ const [pathAndQuery, hash3 = ""] = splitOnce(url, "#");
1766
+ const [rawPath, query = ""] = splitOnce(pathAndQuery, "?");
1767
+ const decodedPath = decodeUriPath(rawPath);
1768
+ if (!decodedPath?.toLowerCase().endsWith(".md")) return null;
1769
+ const resolvedSlug = resolveRelativeMarkdownSlug(
1770
+ context.sourceSlug,
1771
+ decodedPath
1772
+ );
1773
+ if (!resolvedSlug) return null;
1774
+ const indexedSlug = getExactContentSlug(context, resolvedSlug);
1775
+ if (!indexedSlug) return null;
1776
+ const suffix = `${query ? `?${query}` : ""}${hash3 ? `#${hash3}` : ""}`;
1777
+ return `${context.resolvePermalink(indexedSlug)}${suffix}`;
1778
+ }
1779
+ function getExactContentSlug(context, slug) {
1780
+ try {
1781
+ context.resolvePermalink(slug);
1782
+ return slug;
1783
+ } catch {
1784
+ return context.contentIndex.get(slug.toLowerCase()) ?? null;
1785
+ }
1786
+ }
1787
+ function isMarkdownContentLink(url) {
1788
+ if (url.startsWith("#") || url.startsWith("//")) return false;
1789
+ if (/^[a-z][a-z0-9+.-]*:/i.test(url)) return false;
1790
+ return true;
1791
+ }
1792
+ function resolveRelativeMarkdownSlug(sourceSlug, targetPath) {
1793
+ const sourceDirectory = sourceSlug.includes("/") ? sourceSlug.slice(0, sourceSlug.lastIndexOf("/")) : "";
1794
+ const baseParts = targetPath.startsWith("/") ? [] : sourceDirectory.split("/");
1795
+ const parts = [...baseParts, ...targetPath.replace(/^\/+/, "").split("/")];
1796
+ const normalizedParts = [];
1797
+ for (const part of parts) {
1798
+ if (!part || part === ".") continue;
1799
+ if (part === "..") {
1800
+ if (normalizedParts.length === 0) return null;
1801
+ normalizedParts.pop();
1802
+ continue;
1803
+ }
1804
+ normalizedParts.push(part);
1805
+ }
1806
+ return normalizedParts.join("/").replace(/\.md$/i, "");
1807
+ }
1808
+ function decodeUriPath(path4) {
1809
+ try {
1810
+ return decodeURI(path4);
1811
+ } catch {
1812
+ return null;
1813
+ }
1814
+ }
1815
+ function splitOnce(value, separator) {
1816
+ const index = value.indexOf(separator);
1817
+ if (index < 0) return [value];
1818
+ return [value.slice(0, index), value.slice(index + separator.length)];
1819
+ }
1704
1820
  function selectHeadingFragment(markdown, heading) {
1705
1821
  const lines = markdown.split(/\r?\n/);
1706
1822
  const targetHeading = heading.trim().toLowerCase();
@@ -1990,6 +2106,53 @@ var PluginRuntime = class {
1990
2106
  collectGeneratedOutputs() {
1991
2107
  return this.generatedOutputs.all();
1992
2108
  }
2109
+ async resolvePage(pathname, manifest, contentIndex) {
2110
+ const matches = [];
2111
+ const normalizedPathname = normalizePagePath(pathname);
2112
+ const context = {
2113
+ ...this.createContext(contentIndex),
2114
+ manifest,
2115
+ pathname: normalizedPathname
2116
+ };
2117
+ for (const plugin of this.plugins()) {
2118
+ for (const pageType of plugin.pageTypes ?? []) {
2119
+ const page = await pageType.resolve(
2120
+ this.createPluginContext(plugin, context)
2121
+ );
2122
+ if (page) {
2123
+ matches.push({
2124
+ page: { ...page, type: pageType.id, pluginName: plugin.name },
2125
+ priority: pageType.priority ?? 0
2126
+ });
2127
+ }
2128
+ }
2129
+ }
2130
+ if (matches.length === 0) return null;
2131
+ const highestPriority = Math.max(...matches.map((match) => match.priority));
2132
+ const highest = matches.filter(
2133
+ (match) => match.priority === highestPriority
2134
+ );
2135
+ if (highest.length !== 1) {
2136
+ throw new Error(
2137
+ `Multiple plugin page types match ${normalizedPathname} at priority ${highestPriority}: ${highest.map((match) => match.page.type).join(", ")}`
2138
+ );
2139
+ }
2140
+ return highest[0].page;
2141
+ }
2142
+ async getPagePaths(manifest, contentIndex) {
2143
+ const paths = [];
2144
+ const context = { ...this.createContext(contentIndex), manifest };
2145
+ for (const plugin of this.plugins()) {
2146
+ for (const pageType of plugin.pageTypes ?? []) {
2147
+ const declared = pageType.paths;
2148
+ if (!declared) continue;
2149
+ paths.push(
2150
+ ...typeof declared === "function" ? await declared(this.createPluginContext(plugin, context)) : declared
2151
+ );
2152
+ }
2153
+ }
2154
+ return [...new Set(paths.map(normalizePagePath))];
2155
+ }
1993
2156
  async collectDiagnostics(contentIndex) {
1994
2157
  const tracer = this.observability().tracer;
1995
2158
  return await tracer.span("diagnostics.run", {}, async () => {
@@ -2095,9 +2258,57 @@ var PluginRuntime = class {
2095
2258
  return this.pipelineOptions.observability ?? noopObservability;
2096
2259
  }
2097
2260
  plugins() {
2098
- return resolvePlugins(this.pipelineOptions.plugins);
2261
+ const plugins = resolvePlugins(this.pipelineOptions.plugins);
2262
+ const pageTypes = /* @__PURE__ */ new Map();
2263
+ for (const plugin of plugins) {
2264
+ const declaredPageTypes = plugin.pageTypes;
2265
+ if (declaredPageTypes !== void 0 && !Array.isArray(declaredPageTypes)) {
2266
+ throw new TypeError(`Plugin ${plugin.name} pageTypes must be an array`);
2267
+ }
2268
+ for (const pageType of declaredPageTypes ?? []) {
2269
+ validatePageType(plugin.name, pageType);
2270
+ const previous = pageTypes.get(pageType.id);
2271
+ if (previous) {
2272
+ throw new Error(
2273
+ `Plugin page type "${pageType.id}" is provided by both ${previous} and ${plugin.name}`
2274
+ );
2275
+ }
2276
+ pageTypes.set(pageType.id, plugin.name);
2277
+ }
2278
+ }
2279
+ return plugins;
2099
2280
  }
2100
2281
  };
2282
+ function validatePageType(pluginName, pageType) {
2283
+ if (typeof pageType !== "object" || pageType === null) {
2284
+ throw new TypeError(`Plugin ${pluginName} pageTypes must contain objects`);
2285
+ }
2286
+ const candidate = pageType;
2287
+ if (typeof candidate.id !== "string" || candidate.id.trim() === "") {
2288
+ throw new TypeError(
2289
+ `Plugin ${pluginName} page type id must be a non-empty string`
2290
+ );
2291
+ }
2292
+ if (typeof candidate.resolve !== "function") {
2293
+ throw new TypeError(
2294
+ `Plugin ${pluginName} page type "${candidate.id}" must provide a resolve function`
2295
+ );
2296
+ }
2297
+ if (candidate.paths !== void 0 && !Array.isArray(candidate.paths) && typeof candidate.paths !== "function") {
2298
+ throw new TypeError(
2299
+ `Plugin ${pluginName} page type "${candidate.id}" paths must be an array or function`
2300
+ );
2301
+ }
2302
+ if (candidate.priority !== void 0 && (typeof candidate.priority !== "number" || !Number.isFinite(candidate.priority))) {
2303
+ throw new TypeError(
2304
+ `Plugin ${pluginName} page type "${candidate.id}" priority must be a finite number`
2305
+ );
2306
+ }
2307
+ }
2308
+ function normalizePagePath(pathname) {
2309
+ const path4 = `/${pathname.split("/").filter(Boolean).join("/")}`;
2310
+ return path4 === "/" ? path4 : path4.replace(/\/$/, "");
2311
+ }
2101
2312
  function countSeverity(diagnostics, severity) {
2102
2313
  return diagnostics.filter((diagnostic) => diagnostic.severity === severity).length;
2103
2314
  }
@@ -2660,7 +2871,8 @@ var ManifestBuilder = class {
2660
2871
  assets: [],
2661
2872
  clientEntries: [],
2662
2873
  diagnostics: [],
2663
- generatedOutputs: []
2874
+ generatedOutputs: [],
2875
+ pagePaths: []
2664
2876
  };
2665
2877
  return { ...manifest, graph: createContentGraph(manifest) };
2666
2878
  }
@@ -2680,7 +2892,10 @@ function createContentIdIndex(entries) {
2680
2892
  return byContentId;
2681
2893
  }
2682
2894
  function getManifestTitle(slug, title) {
2683
- return typeof title === "string" && title.trim() ? title : slug;
2895
+ if (typeof title === "string" && title.trim()) {
2896
+ return title.trim();
2897
+ }
2898
+ return slug.split("/").at(-1) ?? slug;
2684
2899
  }
2685
2900
  function uniqueAssets(assets) {
2686
2901
  return Array.from(
@@ -2858,6 +3073,9 @@ var ContentManager = class {
2858
3073
  this.locationResolver.populateRedirects(manifest, locations);
2859
3074
  await this.pluginRuntime.runManifestCreated(manifest, contentIndex);
2860
3075
  this.applyPublicView(manifest);
3076
+ manifest.pagePaths = [
3077
+ ...await this.pluginRuntime.getPagePaths(manifest, contentIndex)
3078
+ ];
2861
3079
  manifest.assets = this.pluginRuntime.collectAssets();
2862
3080
  manifest.clientEntries = this.pluginRuntime.collectClientEntries();
2863
3081
  manifest.diagnostics = [
@@ -2890,6 +3108,26 @@ var ContentManager = class {
2890
3108
  const manifest = await this.getManifest();
2891
3109
  return manifest.diagnostics;
2892
3110
  }
3111
+ /** Resolves a page contributed by an enabled plugin. */
3112
+ async resolvePage(pathname) {
3113
+ const [manifest, contentIndex] = await Promise.all([
3114
+ this.getManifest(),
3115
+ this.getContentIndex()
3116
+ ]);
3117
+ return await this.pluginRuntime.resolvePage(
3118
+ pathname,
3119
+ manifest,
3120
+ contentIndex
3121
+ );
3122
+ }
3123
+ /** Returns plugin page paths for SSG enumeration. */
3124
+ async getPagePaths() {
3125
+ const [manifest, contentIndex] = await Promise.all([
3126
+ this.getManifest(),
3127
+ this.getContentIndex()
3128
+ ]);
3129
+ return await this.pluginRuntime.getPagePaths(manifest, contentIndex);
3130
+ }
2893
3131
  async inspect() {
2894
3132
  const [entries, contentIndex] = await Promise.all([
2895
3133
  this.entryReader.getEntries(),
@@ -3139,8 +3377,10 @@ export {
3139
3377
  buildContentCollections,
3140
3378
  buildGraphEdges,
3141
3379
  calculateReadingTime,
3380
+ collectConfigDeprecationDiagnostics,
3142
3381
  createClientEntry,
3143
3382
  createContentGraph,
3383
+ createDeprecationDiagnostic,
3144
3384
  createPluginMemo,
3145
3385
  createStyleAsset,
3146
3386
  createUnavailableGeneratedOutputSink,
@@ -3153,6 +3393,7 @@ export {
3153
3393
  escapeScriptJson,
3154
3394
  extractFrontmatterAliases,
3155
3395
  fingerprintContentEntries,
3396
+ formatDeprecationMessage,
3156
3397
  getContentSourceEntry,
3157
3398
  getExtension,
3158
3399
  getResolvedPluginMetadata,
@@ -0,0 +1,8 @@
1
+ import { type DeprecationNotice } from "./deprecation.js";
2
+ import type { Diagnostic } from "./types/diagnostic.js";
3
+ import type { RiebeckiteConfig } from "./types/riebeckite_config.js";
4
+ export type ConfigDeprecationNotice = DeprecationNotice & {
5
+ readonly kind: "config";
6
+ readonly path: string;
7
+ };
8
+ export declare function collectConfigDeprecationDiagnostics(config: RiebeckiteConfig, notices?: readonly ConfigDeprecationNotice[]): Diagnostic[];
@@ -1,6 +1,7 @@
1
1
  import type { PipelineOptions } from "../pipeline.js";
2
2
  import type { ContentManifest, ContentPublicLocation } from "../types/content_manifest.js";
3
3
  import type { Diagnostic } from "../types/diagnostic.js";
4
+ import type { ResolvedPluginPage } from "../types/plugin_page.js";
4
5
  import type { PostContent } from "../types/post_content.js";
5
6
  import type { ResolvedRiebeckiteConfig } from "../types/resolved_riebeckite_config.js";
6
7
  import { type ContentBuildPreparation } from "./content_build_coordinator.js";
@@ -45,6 +46,10 @@ export declare class ContentManager {
45
46
  getBacklinks(targetSlug: string): Promise<Backlink[]>;
46
47
  getContentGraph(): Promise<ContentGraph>;
47
48
  getDiagnostics(): Promise<Diagnostic[]>;
49
+ /** Resolves a page contributed by an enabled plugin. */
50
+ resolvePage(pathname: string): Promise<ResolvedPluginPage | null>;
51
+ /** Returns plugin page paths for SSG enumeration. */
52
+ getPagePaths(): Promise<readonly string[]>;
48
53
  inspect(): Promise<ContentInspection>;
49
54
  dispose(): Promise<void>;
50
55
  getContentLocations(): Promise<ReadonlyMap<string, ContentPublicLocation>>;
@@ -0,0 +1,14 @@
1
+ import type { Diagnostic } from "./types/diagnostic.js";
2
+ export type DeprecationKind = "config" | "plugin-api" | "theme-api" | "cli" | "scaffold" | "package";
3
+ export type DeprecationNotice = {
4
+ readonly kind: DeprecationKind;
5
+ readonly id: string;
6
+ readonly target: string;
7
+ readonly deprecatedSince: string;
8
+ readonly action: string;
9
+ readonly replacement?: string;
10
+ readonly documentationUrl?: string;
11
+ readonly removedIn?: string;
12
+ };
13
+ export declare function createDeprecationDiagnostic(notice: DeprecationNotice): Diagnostic;
14
+ export declare function formatDeprecationMessage(notice: DeprecationNotice): string;
@@ -3,6 +3,7 @@ import type { ContentLocationInput, ContentManifest, ContentManifestEntry, Conte
3
3
  import type { Diagnostic } from "../types/diagnostic.js";
4
4
  import type { GeneratedOutput } from "../types/generated_output.js";
5
5
  import { type PluginClientEntry } from "../types/plugin_asset.js";
6
+ import type { ResolvedPluginPage } from "../types/plugin_page.js";
6
7
  import type { PostContent } from "../types/post_content.js";
7
8
  /**
8
9
  * Explicit build lifecycle stages. `idle` means no build lifecycle has been
@@ -53,6 +54,8 @@ export declare class PluginRuntime {
53
54
  collectClientEntries(): PluginClientEntry[];
54
55
  /** Returns registered outputs sorted by path for a deterministic build. */
55
56
  collectGeneratedOutputs(): GeneratedOutput[];
57
+ resolvePage(pathname: string, manifest: ContentManifest, contentIndex: Map<string, string>): Promise<ResolvedPluginPage | null>;
58
+ getPagePaths(manifest: ContentManifest, contentIndex: Map<string, string>): Promise<readonly string[]>;
56
59
  collectDiagnostics(contentIndex: Map<string, string>): Promise<Diagnostic[]>;
57
60
  private runBuildLifecycle;
58
61
  private createContext;
@@ -112,4 +112,5 @@ export type ContentManifest = {
112
112
  clientEntries: ContentManifestPluginClientEntry[];
113
113
  diagnostics: Diagnostic[];
114
114
  generatedOutputs: GeneratedOutput[];
115
+ pagePaths: string[];
115
116
  };
@@ -3,6 +3,7 @@ import type { Diagnostic } from "./diagnostic.js";
3
3
  import type { PluginAsset, PluginClientEntry } from "./plugin_asset.js";
4
4
  import type { PluginContentContext, PluginContentLocationAugmentContext, PluginContentLocationResolver, PluginContentRenderer, PluginContext, PluginGeneratedHtmlInspector, PluginGraphContext, PluginLifecycleContext, PluginManifestContext, PluginPostContext } from "./plugin_context.js";
5
5
  import type { PluginEndpoint } from "./plugin_endpoint.js";
6
+ import type { PluginPageType } from "./plugin_page.js";
6
7
  import type { HtmlPipeline, MarkdownPipeline, MarkdownPipelineContext, PipelinePlugin } from "./plugin_pipeline.js";
7
8
  import type { PluginSeoExtension } from "./plugin_seo.js";
8
9
  export type RiebeckitePlugin<TOptions = unknown> = {
@@ -57,6 +58,8 @@ export type RiebeckitePlugin<TOptions = unknown> = {
57
58
  seo?: PluginSeoExtension;
58
59
  extendContentGraph?(context: PluginGraphContext): void | Promise<void>;
59
60
  renderers?: PluginContentRenderer[];
61
+ /** Framework-independent page types contributed by this plugin. */
62
+ pageTypes?: PluginPageType[];
60
63
  };
61
64
  export type PluginInput = RiebeckitePlugin | false | null | undefined;
62
65
  export type ResolvedPluginMetadata = {
@@ -0,0 +1,36 @@
1
+ import type { ContentManifest } from "./content_manifest.js";
2
+ import type { PluginContext } from "./plugin_context.js";
3
+ import type { PluginHeadTag } from "./plugin_head.js";
4
+ /** A framework-independent page supplied by a Riebeckite plugin. */
5
+ export type PluginPage = {
6
+ /** Stable identifier of the page type that produced this page. */
7
+ type: string;
8
+ /** Canonical, absolute URL path. */
9
+ pathname: string;
10
+ /** HTML for the page body. The site owns the surrounding document frame. */
11
+ body: string;
12
+ title?: string;
13
+ description?: string;
14
+ /** Optional document metadata rendered by the site's document frame. */
15
+ headTags?: readonly PluginHeadTag[];
16
+ };
17
+ export type PluginPageContext = PluginContext & {
18
+ /** The public manifest. Unpublished content is not exposed through pages. */
19
+ manifest: ContentManifest;
20
+ /** Request pathname, normalized to an absolute path. */
21
+ pathname: string;
22
+ };
23
+ export type PluginPageType = {
24
+ /** Globally unique, stable page type identifier. */
25
+ id: string;
26
+ /** Paths emitted by SSG for this type. */
27
+ paths?: readonly string[] | ((context: PluginContext & {
28
+ manifest: ContentManifest;
29
+ }) => readonly string[] | Promise<readonly string[]>);
30
+ /** Higher values win when more than one type matches a request. */
31
+ priority?: number;
32
+ resolve(context: PluginPageContext): PluginPage | null | Promise<PluginPage | null>;
33
+ };
34
+ export type ResolvedPluginPage = PluginPage & {
35
+ pluginName: string;
36
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riebeckite/core",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -12,6 +12,12 @@
12
12
  "source": "./index.ts",
13
13
  "default": "./dist/index.js"
14
14
  },
15
+ "./client": {
16
+ "types": "./dist/client.d.ts",
17
+ "import": "./dist/client.js",
18
+ "source": "./client.ts",
19
+ "default": "./dist/client.js"
20
+ },
15
21
  "./package.json": "./package.json"
16
22
  },
17
23
  "files": [
@@ -43,7 +49,7 @@
43
49
  },
44
50
  "devDependencies": {
45
51
  "tsx": "^4.22.4",
46
- "@riebeckite/test": "0.0.12"
52
+ "@riebeckite/test": "0.0.14"
47
53
  },
48
54
  "repository": {
49
55
  "type": "git",