@riebeckite/core 0.0.12 → 0.0.13
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 +94 -94
- package/README_ja.md +75 -75
- package/dist/client.d.ts +2 -0
- package/dist/client.js +43 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +196 -2
- package/dist/src/content/content_manager.d.ts +5 -0
- package/dist/src/plugin/plugin_runtime.d.ts +3 -0
- package/dist/src/types/plugin.d.ts +3 -0
- package/dist/src/types/plugin_page.d.ts +36 -0
- package/package.json +8 -2
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/
|
|
92
|
-
- [Content System](../../docs/en/content-system.md)
|
|
93
|
-
- [Plugin System](../../docs/en/plugin-
|
|
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/
|
|
73
|
-
- [Content System](../../docs/ja/content-system.md)
|
|
74
|
-
- [Plugin System](../../docs/ja/plugin-
|
|
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)
|
package/dist/client.d.ts
ADDED
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
|
@@ -47,6 +47,7 @@ export type { PluginDiagnostic, PluginDiagnosticLevel, } from "./src/types/plugi
|
|
|
47
47
|
export type { PluginEndpoint, PluginEndpointContext, PluginEndpointMethod, PluginEndpointOptions, PluginEndpointRequest, PluginEndpointResponse, } from "./src/types/plugin_endpoint.js";
|
|
48
48
|
export { defineEndpoint } from "./src/types/plugin_endpoint.js";
|
|
49
49
|
export type { PluginHeadTag } from "./src/types/plugin_head.js";
|
|
50
|
+
export type { PluginPage, PluginPageContext, PluginPageType, ResolvedPluginPage, } from "./src/types/plugin_page.js";
|
|
50
51
|
export type { HtmlPipeline, MarkdownEmbedFragment, MarkdownExecutionContext, MarkdownPipeline, MarkdownPipelineContext, PipelinePlugin, } from "./src/types/plugin_pipeline.js";
|
|
51
52
|
export type { PluginSeoExtension, RenderableFeedEntry, SeoMetadata, WebsiteSeoInput, } from "./src/types/plugin_seo.js";
|
|
52
53
|
export type { PostContent, PostFrontmatter } from "./src/types/post_content.js";
|
package/dist/index.js
CHANGED
|
@@ -1588,6 +1588,7 @@ var Pipeline = class {
|
|
|
1588
1588
|
markdownPipelineContext
|
|
1589
1589
|
);
|
|
1590
1590
|
}
|
|
1591
|
+
this.use(processor, normalizeMarkdownLinks, markdownPipelineContext);
|
|
1591
1592
|
this.use(processor, remarkRehype, { allowDangerousHtml: true });
|
|
1592
1593
|
this.use(processor, rehypeRaw);
|
|
1593
1594
|
for (const plugin of plugins) {
|
|
@@ -1701,6 +1702,81 @@ function selectBlockFragment(markdown, blockId) {
|
|
|
1701
1702
|
const line = lines.find((currentLine) => blockIdRe.test(currentLine));
|
|
1702
1703
|
return line?.replace(blockIdRe, "").trimEnd() || null;
|
|
1703
1704
|
}
|
|
1705
|
+
function normalizeMarkdownLinks(context) {
|
|
1706
|
+
return (tree) => {
|
|
1707
|
+
visitMarkdownLinkNodes(tree, (node) => {
|
|
1708
|
+
const normalizedUrl = normalizeMarkdownLinkUrl(node.url, context);
|
|
1709
|
+
if (normalizedUrl) node.url = normalizedUrl;
|
|
1710
|
+
});
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
function visitMarkdownLinkNodes(node, visitor) {
|
|
1714
|
+
const linkNode = node;
|
|
1715
|
+
if ((node.type === "link" || node.type === "definition") && typeof linkNode.url === "string") {
|
|
1716
|
+
visitor(linkNode);
|
|
1717
|
+
}
|
|
1718
|
+
for (const child of linkNode.children ?? []) {
|
|
1719
|
+
visitMarkdownLinkNodes(child, visitor);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
function normalizeMarkdownLinkUrl(url, context) {
|
|
1723
|
+
if (!context.sourceSlug) return null;
|
|
1724
|
+
if (!isMarkdownContentLink(url)) return null;
|
|
1725
|
+
const [pathAndQuery, hash3 = ""] = splitOnce(url, "#");
|
|
1726
|
+
const [rawPath, query = ""] = splitOnce(pathAndQuery, "?");
|
|
1727
|
+
const decodedPath = decodeUriPath(rawPath);
|
|
1728
|
+
if (!decodedPath?.toLowerCase().endsWith(".md")) return null;
|
|
1729
|
+
const resolvedSlug = resolveRelativeMarkdownSlug(
|
|
1730
|
+
context.sourceSlug,
|
|
1731
|
+
decodedPath
|
|
1732
|
+
);
|
|
1733
|
+
if (!resolvedSlug) return null;
|
|
1734
|
+
const indexedSlug = getExactContentSlug(context, resolvedSlug);
|
|
1735
|
+
if (!indexedSlug) return null;
|
|
1736
|
+
const suffix = `${query ? `?${query}` : ""}${hash3 ? `#${hash3}` : ""}`;
|
|
1737
|
+
return `${context.resolvePermalink(indexedSlug)}${suffix}`;
|
|
1738
|
+
}
|
|
1739
|
+
function getExactContentSlug(context, slug) {
|
|
1740
|
+
try {
|
|
1741
|
+
context.resolvePermalink(slug);
|
|
1742
|
+
return slug;
|
|
1743
|
+
} catch {
|
|
1744
|
+
return context.contentIndex.get(slug.toLowerCase()) ?? null;
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
function isMarkdownContentLink(url) {
|
|
1748
|
+
if (url.startsWith("#") || url.startsWith("//")) return false;
|
|
1749
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(url)) return false;
|
|
1750
|
+
return true;
|
|
1751
|
+
}
|
|
1752
|
+
function resolveRelativeMarkdownSlug(sourceSlug, targetPath) {
|
|
1753
|
+
const sourceDirectory = sourceSlug.includes("/") ? sourceSlug.slice(0, sourceSlug.lastIndexOf("/")) : "";
|
|
1754
|
+
const baseParts = targetPath.startsWith("/") ? [] : sourceDirectory.split("/");
|
|
1755
|
+
const parts = [...baseParts, ...targetPath.replace(/^\/+/, "").split("/")];
|
|
1756
|
+
const normalizedParts = [];
|
|
1757
|
+
for (const part of parts) {
|
|
1758
|
+
if (!part || part === ".") continue;
|
|
1759
|
+
if (part === "..") {
|
|
1760
|
+
if (normalizedParts.length === 0) return null;
|
|
1761
|
+
normalizedParts.pop();
|
|
1762
|
+
continue;
|
|
1763
|
+
}
|
|
1764
|
+
normalizedParts.push(part);
|
|
1765
|
+
}
|
|
1766
|
+
return normalizedParts.join("/").replace(/\.md$/i, "");
|
|
1767
|
+
}
|
|
1768
|
+
function decodeUriPath(path4) {
|
|
1769
|
+
try {
|
|
1770
|
+
return decodeURI(path4);
|
|
1771
|
+
} catch {
|
|
1772
|
+
return null;
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
function splitOnce(value, separator) {
|
|
1776
|
+
const index = value.indexOf(separator);
|
|
1777
|
+
if (index < 0) return [value];
|
|
1778
|
+
return [value.slice(0, index), value.slice(index + separator.length)];
|
|
1779
|
+
}
|
|
1704
1780
|
function selectHeadingFragment(markdown, heading) {
|
|
1705
1781
|
const lines = markdown.split(/\r?\n/);
|
|
1706
1782
|
const targetHeading = heading.trim().toLowerCase();
|
|
@@ -1990,6 +2066,53 @@ var PluginRuntime = class {
|
|
|
1990
2066
|
collectGeneratedOutputs() {
|
|
1991
2067
|
return this.generatedOutputs.all();
|
|
1992
2068
|
}
|
|
2069
|
+
async resolvePage(pathname, manifest, contentIndex) {
|
|
2070
|
+
const matches = [];
|
|
2071
|
+
const normalizedPathname = normalizePagePath(pathname);
|
|
2072
|
+
const context = {
|
|
2073
|
+
...this.createContext(contentIndex),
|
|
2074
|
+
manifest,
|
|
2075
|
+
pathname: normalizedPathname
|
|
2076
|
+
};
|
|
2077
|
+
for (const plugin of this.plugins()) {
|
|
2078
|
+
for (const pageType of plugin.pageTypes ?? []) {
|
|
2079
|
+
const page = await pageType.resolve(
|
|
2080
|
+
this.createPluginContext(plugin, context)
|
|
2081
|
+
);
|
|
2082
|
+
if (page) {
|
|
2083
|
+
matches.push({
|
|
2084
|
+
page: { ...page, type: pageType.id, pluginName: plugin.name },
|
|
2085
|
+
priority: pageType.priority ?? 0
|
|
2086
|
+
});
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
if (matches.length === 0) return null;
|
|
2091
|
+
const highestPriority = Math.max(...matches.map((match) => match.priority));
|
|
2092
|
+
const highest = matches.filter(
|
|
2093
|
+
(match) => match.priority === highestPriority
|
|
2094
|
+
);
|
|
2095
|
+
if (highest.length !== 1) {
|
|
2096
|
+
throw new Error(
|
|
2097
|
+
`Multiple plugin page types match ${normalizedPathname} at priority ${highestPriority}: ${highest.map((match) => match.page.type).join(", ")}`
|
|
2098
|
+
);
|
|
2099
|
+
}
|
|
2100
|
+
return highest[0].page;
|
|
2101
|
+
}
|
|
2102
|
+
async getPagePaths(manifest, contentIndex) {
|
|
2103
|
+
const paths = [];
|
|
2104
|
+
const context = { ...this.createContext(contentIndex), manifest };
|
|
2105
|
+
for (const plugin of this.plugins()) {
|
|
2106
|
+
for (const pageType of plugin.pageTypes ?? []) {
|
|
2107
|
+
const declared = pageType.paths;
|
|
2108
|
+
if (!declared) continue;
|
|
2109
|
+
paths.push(
|
|
2110
|
+
...typeof declared === "function" ? await declared(this.createPluginContext(plugin, context)) : declared
|
|
2111
|
+
);
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
return [...new Set(paths.map(normalizePagePath))];
|
|
2115
|
+
}
|
|
1993
2116
|
async collectDiagnostics(contentIndex) {
|
|
1994
2117
|
const tracer = this.observability().tracer;
|
|
1995
2118
|
return await tracer.span("diagnostics.run", {}, async () => {
|
|
@@ -2095,9 +2218,57 @@ var PluginRuntime = class {
|
|
|
2095
2218
|
return this.pipelineOptions.observability ?? noopObservability;
|
|
2096
2219
|
}
|
|
2097
2220
|
plugins() {
|
|
2098
|
-
|
|
2221
|
+
const plugins = resolvePlugins(this.pipelineOptions.plugins);
|
|
2222
|
+
const pageTypes = /* @__PURE__ */ new Map();
|
|
2223
|
+
for (const plugin of plugins) {
|
|
2224
|
+
const declaredPageTypes = plugin.pageTypes;
|
|
2225
|
+
if (declaredPageTypes !== void 0 && !Array.isArray(declaredPageTypes)) {
|
|
2226
|
+
throw new TypeError(`Plugin ${plugin.name} pageTypes must be an array`);
|
|
2227
|
+
}
|
|
2228
|
+
for (const pageType of declaredPageTypes ?? []) {
|
|
2229
|
+
validatePageType(plugin.name, pageType);
|
|
2230
|
+
const previous = pageTypes.get(pageType.id);
|
|
2231
|
+
if (previous) {
|
|
2232
|
+
throw new Error(
|
|
2233
|
+
`Plugin page type "${pageType.id}" is provided by both ${previous} and ${plugin.name}`
|
|
2234
|
+
);
|
|
2235
|
+
}
|
|
2236
|
+
pageTypes.set(pageType.id, plugin.name);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
return plugins;
|
|
2099
2240
|
}
|
|
2100
2241
|
};
|
|
2242
|
+
function validatePageType(pluginName, pageType) {
|
|
2243
|
+
if (typeof pageType !== "object" || pageType === null) {
|
|
2244
|
+
throw new TypeError(`Plugin ${pluginName} pageTypes must contain objects`);
|
|
2245
|
+
}
|
|
2246
|
+
const candidate = pageType;
|
|
2247
|
+
if (typeof candidate.id !== "string" || candidate.id.trim() === "") {
|
|
2248
|
+
throw new TypeError(
|
|
2249
|
+
`Plugin ${pluginName} page type id must be a non-empty string`
|
|
2250
|
+
);
|
|
2251
|
+
}
|
|
2252
|
+
if (typeof candidate.resolve !== "function") {
|
|
2253
|
+
throw new TypeError(
|
|
2254
|
+
`Plugin ${pluginName} page type "${candidate.id}" must provide a resolve function`
|
|
2255
|
+
);
|
|
2256
|
+
}
|
|
2257
|
+
if (candidate.paths !== void 0 && !Array.isArray(candidate.paths) && typeof candidate.paths !== "function") {
|
|
2258
|
+
throw new TypeError(
|
|
2259
|
+
`Plugin ${pluginName} page type "${candidate.id}" paths must be an array or function`
|
|
2260
|
+
);
|
|
2261
|
+
}
|
|
2262
|
+
if (candidate.priority !== void 0 && (typeof candidate.priority !== "number" || !Number.isFinite(candidate.priority))) {
|
|
2263
|
+
throw new TypeError(
|
|
2264
|
+
`Plugin ${pluginName} page type "${candidate.id}" priority must be a finite number`
|
|
2265
|
+
);
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
function normalizePagePath(pathname) {
|
|
2269
|
+
const path4 = `/${pathname.split("/").filter(Boolean).join("/")}`;
|
|
2270
|
+
return path4 === "/" ? path4 : path4.replace(/\/$/, "");
|
|
2271
|
+
}
|
|
2101
2272
|
function countSeverity(diagnostics, severity) {
|
|
2102
2273
|
return diagnostics.filter((diagnostic) => diagnostic.severity === severity).length;
|
|
2103
2274
|
}
|
|
@@ -2680,7 +2851,10 @@ function createContentIdIndex(entries) {
|
|
|
2680
2851
|
return byContentId;
|
|
2681
2852
|
}
|
|
2682
2853
|
function getManifestTitle(slug, title) {
|
|
2683
|
-
|
|
2854
|
+
if (typeof title === "string" && title.trim()) {
|
|
2855
|
+
return title.trim();
|
|
2856
|
+
}
|
|
2857
|
+
return slug.split("/").at(-1) ?? slug;
|
|
2684
2858
|
}
|
|
2685
2859
|
function uniqueAssets(assets) {
|
|
2686
2860
|
return Array.from(
|
|
@@ -2890,6 +3064,26 @@ var ContentManager = class {
|
|
|
2890
3064
|
const manifest = await this.getManifest();
|
|
2891
3065
|
return manifest.diagnostics;
|
|
2892
3066
|
}
|
|
3067
|
+
/** Resolves a page contributed by an enabled plugin. */
|
|
3068
|
+
async resolvePage(pathname) {
|
|
3069
|
+
const [manifest, contentIndex] = await Promise.all([
|
|
3070
|
+
this.getManifest(),
|
|
3071
|
+
this.getContentIndex()
|
|
3072
|
+
]);
|
|
3073
|
+
return await this.pluginRuntime.resolvePage(
|
|
3074
|
+
pathname,
|
|
3075
|
+
manifest,
|
|
3076
|
+
contentIndex
|
|
3077
|
+
);
|
|
3078
|
+
}
|
|
3079
|
+
/** Returns plugin page paths for SSG enumeration. */
|
|
3080
|
+
async getPagePaths() {
|
|
3081
|
+
const [manifest, contentIndex] = await Promise.all([
|
|
3082
|
+
this.getManifest(),
|
|
3083
|
+
this.getContentIndex()
|
|
3084
|
+
]);
|
|
3085
|
+
return await this.pluginRuntime.getPagePaths(manifest, contentIndex);
|
|
3086
|
+
}
|
|
2893
3087
|
async inspect() {
|
|
2894
3088
|
const [entries, contentIndex] = await Promise.all([
|
|
2895
3089
|
this.entryReader.getEntries(),
|
|
@@ -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>>;
|
|
@@ -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;
|
|
@@ -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.
|
|
3
|
+
"version": "0.0.13",
|
|
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.
|
|
52
|
+
"@riebeckite/test": "0.0.13"
|
|
47
53
|
},
|
|
48
54
|
"repository": {
|
|
49
55
|
"type": "git",
|