@riebeckite/core 0.0.30 → 0.0.32

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/dist/client.d.ts CHANGED
@@ -1,2 +1,4 @@
1
+ export type { ClientCleanup, ClientInitializer, ClientInitializerContext, ClientLifecycleErrorContext, ClientLifecycleMount, ClientLifecycleOptions, ClientLifecycleScope, ClientResourceScope, } from "./src/client_lifecycle.js";
2
+ export { ClientLifecycle } from "./src/client_lifecycle.js";
1
3
  export type { ForceGraphLayoutOptions, ForceLayoutGuardOptions, GraphEdge, GraphLayoutNode, LinkableGraphNode, RadialGraphLayoutOptions, } from "./src/content/graph_layout.js";
2
4
  export { buildGraphEdges, FORCE_LAYOUT_CONFIRM_NODE_COUNT, layoutForceGraph, layoutRadialGraph, shouldGuardForceLayout, } from "./src/content/graph_layout.js";
package/dist/client.js CHANGED
@@ -1,3 +1,179 @@
1
+ // src/client_lifecycle.ts
2
+ var ClientLifecycle = class {
3
+ mounted = /* @__PURE__ */ new Map();
4
+ scopes = /* @__PURE__ */ new Map();
5
+ onError;
6
+ constructor(options = {}) {
7
+ this.onError = options.onError ?? defaultErrorReporter;
8
+ }
9
+ mount(mount) {
10
+ const scoped = this.scoped(mount.scope);
11
+ const current = scoped.get(mount.name);
12
+ if (current) {
13
+ if (current.root === mount.root) return current.ready;
14
+ throw new Error(
15
+ `Client initializer "${mount.name}" is already mounted in ${mount.scope} scope.`
16
+ );
17
+ }
18
+ const scope = this.scopeState(mount.scope);
19
+ const resources = new InitializerResources(
20
+ scope.controller,
21
+ (error) => this.report(error, {
22
+ scope: mount.scope,
23
+ name: mount.name,
24
+ phase: "cleanup"
25
+ })
26
+ );
27
+ const cleanup = this.initialize(mount, resources);
28
+ const ready = cleanup.then(() => void 0);
29
+ const mounted = { root: mount.root, resources, cleanup, ready };
30
+ scoped.set(mount.name, mounted);
31
+ return ready;
32
+ }
33
+ async unmount(scope) {
34
+ const scoped = this.mounted.get(scope);
35
+ const state = this.scopes.get(scope);
36
+ this.mounted.delete(scope);
37
+ this.scopes.delete(scope);
38
+ state?.controller.abort();
39
+ if (!scoped) return;
40
+ await Promise.all(
41
+ [...scoped.entries()].map(
42
+ ([name, mounted]) => this.cleanupMounted(scope, name, mounted)
43
+ )
44
+ );
45
+ }
46
+ async dispose() {
47
+ await Promise.all([this.unmount("page"), this.unmount("app")]);
48
+ }
49
+ scoped(scope) {
50
+ let scoped = this.mounted.get(scope);
51
+ if (!scoped) {
52
+ scoped = /* @__PURE__ */ new Map();
53
+ this.mounted.set(scope, scoped);
54
+ }
55
+ return scoped;
56
+ }
57
+ scopeState(scope) {
58
+ let state = this.scopes.get(scope);
59
+ if (!state) {
60
+ state = { controller: new AbortController() };
61
+ this.scopes.set(scope, state);
62
+ }
63
+ return state;
64
+ }
65
+ async initialize(mount, resources) {
66
+ try {
67
+ const result = await mount.initialize({
68
+ root: mount.root,
69
+ signal: resources.signal,
70
+ scope: resources,
71
+ config: mount.config
72
+ });
73
+ return typeof result === "function" ? result : void 0;
74
+ } catch (error) {
75
+ await resources.dispose();
76
+ this.report(error, {
77
+ scope: mount.scope,
78
+ name: mount.name,
79
+ phase: "initialize"
80
+ });
81
+ return void 0;
82
+ }
83
+ }
84
+ async cleanupMounted(scope, name, mounted) {
85
+ const [cleanup] = await Promise.all([
86
+ mounted.cleanup,
87
+ mounted.resources.dispose()
88
+ ]);
89
+ try {
90
+ await cleanup?.();
91
+ } catch (error) {
92
+ this.report(error, { scope, name, phase: "cleanup" });
93
+ }
94
+ }
95
+ report(error, context) {
96
+ try {
97
+ this.onError(error, context);
98
+ } catch {
99
+ return;
100
+ }
101
+ }
102
+ };
103
+ var InitializerResources = class {
104
+ constructor(controller, report) {
105
+ this.controller = controller;
106
+ this.report = report;
107
+ }
108
+ controller;
109
+ report;
110
+ cleanups = [];
111
+ listeners = [];
112
+ timers = /* @__PURE__ */ new Set();
113
+ intervals = /* @__PURE__ */ new Set();
114
+ disposed = false;
115
+ get signal() {
116
+ return this.controller.signal;
117
+ }
118
+ on(target, type, listener, options) {
119
+ if (this.disposed || this.signal.aborted) return;
120
+ target.addEventListener(type, listener, {
121
+ ...options,
122
+ signal: this.signal
123
+ });
124
+ this.listeners.push({ target, type, listener, options });
125
+ }
126
+ timeout(callback, delay = 0) {
127
+ if (this.disposed || this.signal.aborted) return;
128
+ const timer = setTimeout(() => {
129
+ this.timers.delete(timer);
130
+ if (!this.disposed && !this.signal.aborted) callback();
131
+ }, delay);
132
+ this.timers.add(timer);
133
+ }
134
+ interval(callback, delay = 0) {
135
+ if (this.disposed || this.signal.aborted) return;
136
+ const interval = setInterval(() => {
137
+ if (!this.disposed && !this.signal.aborted) callback();
138
+ }, delay);
139
+ this.intervals.add(interval);
140
+ }
141
+ cleanup(cleanup) {
142
+ if (this.disposed) {
143
+ void this.run(cleanup);
144
+ return;
145
+ }
146
+ this.cleanups.push(cleanup);
147
+ }
148
+ async dispose() {
149
+ if (this.disposed) return;
150
+ this.disposed = true;
151
+ for (const { target, type, listener, options } of this.listeners) {
152
+ target.removeEventListener(type, listener, options);
153
+ }
154
+ this.listeners.length = 0;
155
+ for (const timer of this.timers) clearTimeout(timer);
156
+ for (const interval of this.intervals) clearInterval(interval);
157
+ this.timers.clear();
158
+ this.intervals.clear();
159
+ const cleanups = this.cleanups.splice(0).reverse();
160
+ await Promise.all(cleanups.map((cleanup) => this.run(cleanup)));
161
+ }
162
+ async run(cleanup) {
163
+ try {
164
+ await cleanup();
165
+ } catch (error) {
166
+ this.report(error);
167
+ }
168
+ }
169
+ };
170
+ function defaultErrorReporter(error, context) {
171
+ console.error(
172
+ `[riebeckite] Client initializer ${context.phase} failed: ${context.name}`,
173
+ error
174
+ );
175
+ }
176
+
1
177
  // src/content/graph_layout.ts
2
178
  var FORCE_LAYOUT_CONFIRM_NODE_COUNT = 500;
3
179
  function shouldGuardForceLayout(options) {
@@ -176,6 +352,7 @@ function clampFinite(value, min, max) {
176
352
  return clamp(value, min, max);
177
353
  }
178
354
  export {
355
+ ClientLifecycle,
179
356
  FORCE_LAYOUT_CONFIRM_NODE_COUNT,
180
357
  buildGraphEdges,
181
358
  layoutForceGraph,
package/dist/index.d.ts CHANGED
@@ -47,6 +47,7 @@ export { PluginDependencyError, type PluginDependencyErrorKind, } from "./src/pl
47
47
  export { PluginHookError } from "./src/plugin/plugin_lifecycle.js";
48
48
  export type { CreatePluginMemoOptions, PluginMemo, } from "./src/plugin/plugin_memo.js";
49
49
  export { createPluginMemo, stableStringify, } from "./src/plugin/plugin_memo.js";
50
+ export { PluginRuntime } from "./src/plugin/plugin_runtime.js";
50
51
  export { resolveBuildOutputDirectory, resolveManagedStateRoot, } from "./src/project/project_layout.js";
51
52
  export type { ConfigValidationIssue, PluginOptionsValidator, } from "./src/types/config_validation.js";
52
53
  export type { ContentAsset, ContentFileMetadata, ContentLink, ContentLinkKind, ContentLocationInput, ContentManifest, ContentManifestEntry, ContentManifestPluginClientEntry, ContentPublicLocation, ContentRedirect, FolderLocation, PluginPageRoute, } from "./src/types/content_manifest.js";
@@ -73,7 +74,7 @@ export type { PostContent, PostFrontmatter } from "./src/types/post_content.js";
73
74
  export type { PublishFrontmatter, PublishStrategy, } from "./src/types/publish_strategy.js";
74
75
  export { isPublishable } from "./src/types/publish_strategy.js";
75
76
  export type { ResolvedRiebeckiteConfig } from "./src/types/resolved_riebeckite_config.js";
76
- export type { RiebeckiteConfig } from "./src/types/riebeckite_config.js";
77
+ export type { RiebeckiteConfig, SpaConfig, } from "./src/types/riebeckite_config.js";
77
78
  export type { SiteConfig } from "./src/types/site_config.js";
78
79
  export type { RiebeckiteTheme, ThemeArticleLayoutPreset, ThemeAttributes, ThemeColorMode, ThemeConfig, ThemeDesignTokens, ThemeInput, ThemeStyle, ThemeTypographyPreset, } from "./src/types/theme_config.js";
79
80
  export { defineTheme } from "./src/types/theme_config.js";
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ function validateConfigIssues(config) {
20
20
  validateSite(value.site, issues);
21
21
  validateContent(value.content, issues);
22
22
  validateCache(value.cache, issues);
23
+ validateSpa(value.spa, issues);
23
24
  validateTheme(value.theme, issues);
24
25
  validatePlugins(value.plugins, issues);
25
26
  return issues;
@@ -103,6 +104,12 @@ function validateCache(value, issues) {
103
104
  validateOptionalBoolean(value.enabled, "cache.enabled", issues);
104
105
  validateOptionalString(value.directory, "cache.directory", issues);
105
106
  }
107
+ function validateSpa(value, issues) {
108
+ if (value === void 0 || typeof value === "boolean" || isRecord(value)) {
109
+ return;
110
+ }
111
+ issues.push({ path: "spa", message: "Expected a boolean or an object." });
112
+ }
106
113
  function validateTheme(value, issues) {
107
114
  if (value === void 0) return;
108
115
  if (!isRecord(value)) {
@@ -680,6 +687,7 @@ function isConfigModuleWrapper(value) {
680
687
  function resolveConfig(config) {
681
688
  validateConfig(config);
682
689
  const theme = resolveThemeConfig(config.theme);
690
+ const spa = resolveSpaConfig(config.spa);
683
691
  return {
684
692
  site: {
685
693
  title: config.site?.title ?? "",
@@ -708,9 +716,14 @@ function resolveConfig(config) {
708
716
  cache: {
709
717
  enabled: config.cache?.enabled ?? true,
710
718
  directory: config.cache?.directory ?? ""
711
- }
719
+ },
720
+ ...spa === void 0 ? {} : { spa }
712
721
  };
713
722
  }
723
+ function resolveSpaConfig(spa) {
724
+ if (spa === void 0 || spa === false) return void 0;
725
+ return spa === true ? {} : spa;
726
+ }
714
727
  function resolveThemeConfig(themeInput) {
715
728
  const theme = themeInput ? isRiebeckiteTheme(themeInput) ? { name: themeInput.name, ...themeInput.config } : themeInput : void 0;
716
729
  const styles = themeInput ? isRiebeckiteTheme(themeInput) ? themeInput.styles ?? [] : [defaultThemeStyle] : [defaultThemeStyle];
@@ -826,7 +839,7 @@ function getFileName(filePath) {
826
839
  }
827
840
 
828
841
  // src/content/content_build_state.ts
829
- var CONTENT_BUILD_STATE_VERSION = 19;
842
+ var CONTENT_BUILD_STATE_VERSION = 20;
830
843
  var CONTENT_BUILD_STATE_DIRECTORY = ".riebeckite";
831
844
  var CONTENT_BUILD_STATE_EXCLUDE = `${CONTENT_BUILD_STATE_DIRECTORY}/**`;
832
845
 
@@ -834,8 +847,11 @@ var CONTENT_BUILD_STATE_EXCLUDE = `${CONTENT_BUILD_STATE_DIRECTORY}/**`;
834
847
  import { randomUUID } from "node:crypto";
835
848
  import fs from "node:fs/promises";
836
849
  import path from "node:path";
850
+ var MAX_CONTENT_BUILD_STATE_BYTES = 64 * 1024 * 1024;
837
851
  async function loadContentBuildState(statePath) {
838
852
  try {
853
+ const metadata = await fs.stat(statePath);
854
+ if (metadata.size > MAX_CONTENT_BUILD_STATE_BYTES) return void 0;
839
855
  const parsed = JSON.parse(await fs.readFile(statePath, "utf8"));
840
856
  return isContentBuildState(parsed) ? parsed : void 0;
841
857
  } catch {
@@ -922,7 +938,7 @@ function isContentBuildStateShape(value) {
922
938
  }
923
939
  function isManifestHookCache(value) {
924
940
  return Array.isArray(value) && value.every(
925
- (entry) => isRecord2(entry) && typeof entry.pluginName === "string" && typeof entry.cacheable === "boolean" && (entry.pipelineFingerprint === void 0 || typeof entry.pipelineFingerprint === "string") && typeof entry.inputFingerprint === "string" && (entry.outputFingerprint === void 0 || typeof entry.outputFingerprint === "string") && (entry.snapshot === void 0 || isManifestHookSnapshot(entry.snapshot))
941
+ (entry) => isRecord2(entry) && typeof entry.pluginName === "string" && typeof entry.cacheVersion === "string" && typeof entry.cacheable === "boolean" && (entry.pipelineFingerprint === void 0 || typeof entry.pipelineFingerprint === "string") && typeof entry.inputFingerprint === "string" && (entry.outputFingerprint === void 0 || typeof entry.outputFingerprint === "string") && (entry.snapshot === void 0 || isManifestHookSnapshot(entry.snapshot))
926
942
  );
927
943
  }
928
944
  function isManifestHookSnapshot(value) {
@@ -2516,6 +2532,34 @@ function rejectInternalRiebeckitePath(normalized, input) {
2516
2532
  }
2517
2533
 
2518
2534
  // src/pipeline.ts
2535
+ function isPluginTable(node) {
2536
+ const className = node.properties?.className;
2537
+ const names = Array.isArray(className) ? className : [className];
2538
+ return names.some(
2539
+ (name) => typeof name === "string" && (name.startsWith("rb-") || name.startsWith("rr-"))
2540
+ );
2541
+ }
2542
+ function wrapMarkdownTables() {
2543
+ return (tree) => {
2544
+ wrapTableChildren(tree);
2545
+ };
2546
+ }
2547
+ function wrapTableChildren(parent) {
2548
+ if (!parent.children) return;
2549
+ parent.children = parent.children.map((child) => {
2550
+ const node = child;
2551
+ if (node.type === "element" && node.tagName === "table" && !isPluginTable(node)) {
2552
+ return {
2553
+ type: "element",
2554
+ tagName: "div",
2555
+ properties: { className: ["rb-table-scroll"] },
2556
+ children: [child]
2557
+ };
2558
+ }
2559
+ wrapTableChildren(node);
2560
+ return child;
2561
+ });
2562
+ }
2519
2563
  var Pipeline = class {
2520
2564
  constructor(contentIndex, permalinks, getMarkdownBySlug, options = {}, isBuildTime = false) {
2521
2565
  this.contentIndex = contentIndex;
@@ -2691,6 +2735,7 @@ var Pipeline = class {
2691
2735
  }
2692
2736
  });
2693
2737
  }
2738
+ this.use(processor, wrapMarkdownTables);
2694
2739
  this.use(processor, rehypeSlug);
2695
2740
  this.use(processor, rehypeKatex, { output: "mathml", strict: false });
2696
2741
  this.use(processor, rehypeFormat);
@@ -2900,12 +2945,14 @@ function createStyleAsset(pluginName, moduleSpecifier) {
2900
2945
  moduleSpecifier
2901
2946
  };
2902
2947
  }
2903
- function createClientEntry(pluginName, moduleSpecifier, exportName, publicConfig) {
2948
+ function createClientEntry(pluginName, moduleSpecifier, exportName, publicConfig, scope, initializerArguments) {
2904
2949
  return {
2905
2950
  pluginName,
2906
2951
  moduleSpecifier,
2907
2952
  ...exportName === void 0 ? {} : { exportName },
2908
- ...publicConfig === void 0 ? {} : { publicConfig }
2953
+ ...publicConfig === void 0 ? {} : { publicConfig },
2954
+ ...scope === void 0 ? {} : { scope },
2955
+ ...initializerArguments === void 0 ? {} : { initializerArguments }
2909
2956
  };
2910
2957
  }
2911
2958
  function serializePublicClientConfig(value) {
@@ -2991,16 +3038,18 @@ function serializeManifestEntries(entries) {
2991
3038
  serialized.set(entry.slug, JSON.stringify(entry));
2992
3039
  return serialized;
2993
3040
  }
2994
- function createManifestHookCacheEntry(pluginName, cacheable, pipelineFingerprint, inputFingerprint, manifest, beforeEntries, afterEntries) {
3041
+ function createManifestHookCacheEntry(pluginName, cacheVersion, cacheable, pipelineFingerprint, inputFingerprint, manifest, beforeEntries, afterEntries) {
2995
3042
  if (!cacheable)
2996
3043
  return {
2997
3044
  pluginName,
3045
+ cacheVersion,
2998
3046
  cacheable,
2999
3047
  pipelineFingerprint,
3000
3048
  inputFingerprint
3001
3049
  };
3002
3050
  return {
3003
3051
  pluginName,
3052
+ cacheVersion,
3004
3053
  cacheable,
3005
3054
  pipelineFingerprint,
3006
3055
  inputFingerprint,
@@ -3282,9 +3331,10 @@ var ManifestHookRunner = class {
3282
3331
  if (plugin.buildEnd) return "plugin-build-end";
3283
3332
  if (plugin.planGeneratedOutputs) return "plugin-generated-output-plan";
3284
3333
  if (!plugin.onManifestCreated) continue;
3334
+ if (!plugin.manifestHookCache) return "manifest-hook-cache-disabled";
3285
3335
  const cached = previousCache[cacheIndex];
3286
3336
  cacheIndex += 1;
3287
- if (cached?.pluginName !== plugin.name || !cached.cacheable || cached.pipelineFingerprint !== pipelineFingerprint)
3337
+ if (cached?.pluginName !== plugin.name || cached.cacheVersion !== plugin.manifestHookCache.version || !cached.cacheable || cached.pipelineFingerprint !== pipelineFingerprint)
3288
3338
  return "manifest-hook-cache-miss";
3289
3339
  }
3290
3340
  return void 0;
@@ -3298,10 +3348,14 @@ var ManifestHookRunner = class {
3298
3348
  manifest
3299
3349
  };
3300
3350
  let cacheIndex = 0;
3301
- let entriesBeforeHook = serializeManifestEntries(manifest.entries);
3302
3351
  for (const plugin of this.options.plugins()) {
3303
3352
  const hook = plugin.onManifestCreated;
3304
3353
  if (!hook) continue;
3354
+ const cacheVersion = plugin.manifestHookCache?.version;
3355
+ if (!cacheVersion) {
3356
+ await this.runManifestHook(plugin, hook, context, "cache-disabled");
3357
+ continue;
3358
+ }
3305
3359
  const inputFingerprint = fingerprintManifestHookInput(manifest);
3306
3360
  const cached = previousCache[cacheIndex];
3307
3361
  cacheIndex += 1;
@@ -3313,46 +3367,29 @@ var ManifestHookRunner = class {
3313
3367
  );
3314
3368
  let restored = false;
3315
3369
  let restorePoint;
3316
- if (cached !== void 0 && cached.pluginName === plugin.name && cached.cacheable && cached.pipelineFingerprint === pipelineFingerprint && cached.inputFingerprint === inputFingerprint && cached.snapshot !== void 0 && cached.outputFingerprint !== void 0) {
3370
+ if (cached !== void 0 && cached.pluginName === plugin.name && cached.cacheVersion === cacheVersion && cached.cacheable && cached.pipelineFingerprint === pipelineFingerprint && cached.inputFingerprint === inputFingerprint && cached.snapshot !== void 0 && cached.outputFingerprint !== void 0) {
3317
3371
  restorePoint = captureManifestHookRestorePoint(manifest);
3318
3372
  restored = applyManifestHookSnapshot(manifest, cached.snapshot);
3319
3373
  if (restored && fingerprintManifestHookSnapshot(manifest) === cached.outputFingerprint) {
3320
3374
  this.manifestHooksReused += 1;
3321
3375
  this.manifestHookCache.push(cached);
3322
3376
  this.recordManifestHookMetric(plugin.name, "reused", "cache-hit", 0);
3323
- entriesBeforeHook = serializeManifestEntries(manifest.entries);
3324
3377
  continue;
3325
3378
  }
3326
3379
  }
3327
3380
  if (restored && restorePoint)
3328
3381
  restoreManifestHookEntries(manifest, restorePoint);
3382
+ const entriesBeforeHook = serializeManifestEntries(manifest.entries);
3329
3383
  const sideEffectsBefore = fingerprintManifestHookSideEffects(manifest);
3330
3384
  const outputCountBefore = this.options.generatedOutputCount();
3331
3385
  const diagnosticCountBefore = this.options.diagnosticCount();
3332
3386
  const manifestDiagnosticCountBefore = manifest.diagnostics.length;
3333
- const started = performance.now();
3334
- this.manifestHooksExecuted += 1;
3335
- try {
3336
- await runPluginHook(
3337
- plugin,
3338
- "onManifestCreated",
3339
- this.options.createPluginContext(plugin, context),
3340
- hook
3341
- );
3342
- } finally {
3343
- const durationMs = performance.now() - started;
3344
- this.manifestHookDurationMs += durationMs;
3345
- this.recordManifestHookMetric(
3346
- plugin.name,
3347
- "executed",
3348
- reason,
3349
- durationMs
3350
- );
3351
- }
3387
+ await this.runManifestHook(plugin, hook, context, reason);
3352
3388
  const entriesAfterHook = serializeManifestEntries(manifest.entries);
3353
3389
  this.manifestHookCache.push(
3354
3390
  createManifestHookCacheEntry(
3355
3391
  plugin.name,
3392
+ cacheVersion,
3356
3393
  plugin.resolveExternalDependencies === void 0 && sideEffectsBefore === fingerprintManifestHookSideEffects(manifest) && outputCountBefore === this.options.generatedOutputCount() && diagnosticCountBefore === this.options.diagnosticCount() && manifestDiagnosticCountBefore === manifest.diagnostics.length,
3357
3394
  pipelineFingerprint,
3358
3395
  inputFingerprint,
@@ -3361,7 +3398,27 @@ var ManifestHookRunner = class {
3361
3398
  entriesAfterHook
3362
3399
  )
3363
3400
  );
3364
- entriesBeforeHook = entriesAfterHook;
3401
+ }
3402
+ }
3403
+ async runManifestHook(plugin, hook, context, reason) {
3404
+ const started = performance.now();
3405
+ this.manifestHooksExecuted += 1;
3406
+ try {
3407
+ await runPluginHook(
3408
+ plugin,
3409
+ "onManifestCreated",
3410
+ this.options.createPluginContext(plugin, context),
3411
+ hook
3412
+ );
3413
+ } finally {
3414
+ const durationMs = performance.now() - started;
3415
+ this.manifestHookDurationMs += durationMs;
3416
+ this.recordManifestHookMetric(
3417
+ plugin.name,
3418
+ "executed",
3419
+ reason,
3420
+ durationMs
3421
+ );
3365
3422
  }
3366
3423
  }
3367
3424
  recordManifestHookMetric(pluginName, outcome, reason, durationMs) {
@@ -3410,7 +3467,8 @@ var PluginPageResolver = class {
3410
3467
  const context = {
3411
3468
  ...this.options.createContext(contentIndex),
3412
3469
  manifest,
3413
- pathname: pagePathname
3470
+ pathname: pagePathname,
3471
+ renderMarkdown: this.options.renderMarkdown
3414
3472
  };
3415
3473
  const page = await pageType.resolve(
3416
3474
  this.options.createPluginContext(plugin, context)
@@ -3465,7 +3523,8 @@ var PluginPageResolver = class {
3465
3523
  const context = {
3466
3524
  ...this.options.createContext(contentIndex),
3467
3525
  manifest,
3468
- pathname
3526
+ pathname,
3527
+ renderMarkdown: this.options.renderMarkdown
3469
3528
  };
3470
3529
  const dependencies = pageType.outputDependencies ? typeof pageType.outputDependencies === "function" ? await pageType.outputDependencies(
3471
3530
  this.options.createPluginContext(plugin, context)
@@ -3605,7 +3664,13 @@ var PluginRuntime = class {
3605
3664
  this.pageResolver = new PluginPageResolver({
3606
3665
  plugins: () => this.plugins(),
3607
3666
  createContext: (contentIndex) => this.createContext(contentIndex),
3608
- createPluginContext: (plugin, context) => this.createPluginContext(plugin, context)
3667
+ createPluginContext: (plugin, context) => this.createPluginContext(plugin, context),
3668
+ renderMarkdown: async (markdown) => {
3669
+ if (!this.pageMarkdownRenderer) {
3670
+ throw new Error("Plugin page Markdown renderer is unavailable.");
3671
+ }
3672
+ return await this.pageMarkdownRenderer(markdown);
3673
+ }
3609
3674
  });
3610
3675
  this.manifestHooks = new ManifestHookRunner({
3611
3676
  plugins: () => this.plugins(),
@@ -3628,9 +3693,13 @@ var PluginRuntime = class {
3628
3693
  externalDependenciesResolved = false;
3629
3694
  pageResolver;
3630
3695
  manifestHooks;
3696
+ pageMarkdownRenderer = null;
3631
3697
  getDiagnostics() {
3632
3698
  return this.diagnostics;
3633
3699
  }
3700
+ setPageMarkdownRenderer(renderer) {
3701
+ this.pageMarkdownRenderer = renderer;
3702
+ }
3634
3703
  getManifestHookMetrics() {
3635
3704
  return this.manifestHooks.getMetrics();
3636
3705
  }
@@ -5628,6 +5697,12 @@ var ContentManager = class {
5628
5697
  this.contentIndexBuilder = new ContentIndexBuilder(this.source);
5629
5698
  this.manifestAssembler = new ManifestAssembler(this.contentCache);
5630
5699
  this.pluginRuntime = new PluginRuntime(this.pipelineOptions);
5700
+ this.pluginRuntime.setPageMarkdownRenderer(async (markdown) => {
5701
+ if (!this.pipeline) {
5702
+ throw new Error("Markdown pipeline is unavailable for plugin pages.");
5703
+ }
5704
+ return (await this.pipeline.execute(markdown)).html;
5705
+ });
5631
5706
  const buildStatePath = resolveContentBuildStatePath(
5632
5707
  pipelineOptions.config,
5633
5708
  typeof content === "string" ? content : void 0
@@ -6026,7 +6101,7 @@ var ContentManager = class {
6026
6101
  /** Resolves a page contributed by an enabled plugin. */
6027
6102
  async resolvePage(pathname) {
6028
6103
  const [manifest, contentIndex] = await Promise.all([
6029
- this.getManifest(),
6104
+ this.getManifest({ incremental: false }),
6030
6105
  this.getContentIndex()
6031
6106
  ]);
6032
6107
  return await this.pluginRuntime.resolvePage(
@@ -6089,7 +6164,7 @@ var ContentManager = class {
6089
6164
  }
6090
6165
  requestBuildStateCommit(options) {
6091
6166
  if (this.pipelineOptions.commitBuildState === false) return;
6092
- if (options?.incremental === true) this.buildStateCommitRequested = true;
6167
+ if (options?.incremental !== false) this.buildStateCommitRequested = true;
6093
6168
  }
6094
6169
  async commitBuildState() {
6095
6170
  if (this.buildStateCommitted || !this.buildStateCommitRequested || !this.manifest || !this.outputChangeSet) {
@@ -6834,6 +6909,7 @@ export {
6834
6909
  Pipeline,
6835
6910
  PluginDependencyError,
6836
6911
  PluginHookError,
6912
+ PluginRuntime,
6837
6913
  SinkTracer,
6838
6914
  attachErrorPath,
6839
6915
  attachmentUrl,
@@ -0,0 +1,45 @@
1
+ export type ClientLifecycleScope = "app" | "page";
2
+ export type ClientCleanup = () => void | Promise<void>;
3
+ export type ClientResourceScope = {
4
+ readonly signal: AbortSignal;
5
+ on(target: EventTarget, type: string, listener: EventListenerOrEventListenerObject, options?: Omit<AddEventListenerOptions, "signal">): void;
6
+ timeout(callback: () => void, delay?: number): void;
7
+ interval(callback: () => void, delay?: number): void;
8
+ cleanup(cleanup: ClientCleanup): void;
9
+ };
10
+ export type ClientInitializerContext<TRoot, TConfig> = {
11
+ root: TRoot;
12
+ signal: AbortSignal;
13
+ scope: ClientResourceScope;
14
+ config: TConfig;
15
+ };
16
+ export type ClientInitializer<TRoot, TConfig> = (context: ClientInitializerContext<TRoot, TConfig>) => undefined | ClientCleanup | Promise<undefined | ClientCleanup>;
17
+ export type ClientLifecycleErrorContext = {
18
+ scope: ClientLifecycleScope;
19
+ name: string;
20
+ phase: "initialize" | "cleanup";
21
+ };
22
+ export type ClientLifecycleOptions = {
23
+ onError?: (error: unknown, context: ClientLifecycleErrorContext) => void;
24
+ };
25
+ export type ClientLifecycleMount<TRoot, TConfig> = {
26
+ scope: ClientLifecycleScope;
27
+ name: string;
28
+ root: TRoot;
29
+ config: TConfig;
30
+ initialize: ClientInitializer<TRoot, TConfig>;
31
+ };
32
+ export declare class ClientLifecycle<TRoot> {
33
+ private readonly mounted;
34
+ private readonly scopes;
35
+ private readonly onError;
36
+ constructor(options?: ClientLifecycleOptions);
37
+ mount<TConfig>(mount: ClientLifecycleMount<TRoot, TConfig>): Promise<void>;
38
+ unmount(scope: ClientLifecycleScope): Promise<void>;
39
+ dispose(): Promise<void>;
40
+ private scoped;
41
+ private scopeState;
42
+ private initialize;
43
+ private cleanupMounted;
44
+ private report;
45
+ }
@@ -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 = 19;
4
+ export declare const CONTENT_BUILD_STATE_VERSION = 20;
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
@@ -33,6 +33,7 @@ export type ContentBuildState = {
33
33
  };
34
34
  export type ManifestHookCacheEntry = {
35
35
  readonly pluginName: string;
36
+ readonly cacheVersion: string;
36
37
  readonly cacheable: boolean;
37
38
  readonly pipelineFingerprint: string | undefined;
38
39
  readonly inputFingerprint: string;
@@ -2,7 +2,7 @@ import type { ContentManifest, ContentManifestEntry } from "../types/content_man
2
2
  import type { ManifestHookCacheEntry, ManifestHookSnapshot } from "./content_build_state.js";
3
3
  export declare function fingerprintManifestHookInput(manifest: ContentManifest): string;
4
4
  export declare function serializeManifestEntries(entries: readonly ContentManifestEntry[]): Map<string, string>;
5
- export declare function createManifestHookCacheEntry(pluginName: string, cacheable: boolean, pipelineFingerprint: string | undefined, inputFingerprint: string, manifest: ContentManifest, beforeEntries: ReadonlyMap<string, string>, afterEntries: ReadonlyMap<string, string>): ManifestHookCacheEntry;
5
+ export declare function createManifestHookCacheEntry(pluginName: string, cacheVersion: string, cacheable: boolean, pipelineFingerprint: string | undefined, inputFingerprint: string, manifest: ContentManifest, beforeEntries: ReadonlyMap<string, string>, afterEntries: ReadonlyMap<string, string>): ManifestHookCacheEntry;
6
6
  export declare function fingerprintManifestHookSideEffects(manifest: ContentManifest): string;
7
7
  export declare function fingerprintManifestHookSnapshot(manifest: ContentManifest): string;
8
8
  export type ManifestHookRestorePoint = {
@@ -37,6 +37,7 @@ export declare class ManifestHookRunner {
37
37
  getNoChangeFastPathBlockReason(previousCache: readonly ManifestHookCacheEntry[], pipelineFingerprint: string | undefined): string | undefined;
38
38
  getCache(): readonly ManifestHookCacheEntry[];
39
39
  runManifestCreated(manifest: ContentManifest, contentIndex: Map<string, string>, previousCache: readonly ManifestHookCacheEntry[], pipelineFingerprint: string | undefined): Promise<void>;
40
+ private runManifestHook;
40
41
  private recordManifestHookMetric;
41
42
  }
42
43
  export {};
@@ -7,6 +7,7 @@ type PluginPageResolverOptions = {
7
7
  readonly plugins: () => readonly RiebeckitePlugin[];
8
8
  readonly createContext: (contentIndex: Map<string, string>) => PluginContextBase;
9
9
  readonly createPluginContext: <TContext extends PluginContextBase>(plugin: RiebeckitePlugin, context: TContext) => PluginContextWithCache<TContext>;
10
+ readonly renderMarkdown: (markdown: string) => Promise<string>;
10
11
  };
11
12
  export declare class PluginPageResolver {
12
13
  private readonly options;
@@ -25,8 +25,10 @@ export declare class PluginRuntime {
25
25
  private externalDependenciesResolved;
26
26
  private readonly pageResolver;
27
27
  private readonly manifestHooks;
28
+ private pageMarkdownRenderer;
28
29
  constructor(pipelineOptions?: PipelineOptions);
29
30
  getDiagnostics(): Diagnostic[];
31
+ setPageMarkdownRenderer(renderer: (markdown: string) => Promise<string>): void;
30
32
  getManifestHookMetrics(): ManifestHookMetrics;
31
33
  getNoChangeFastPathBlockReason(previousCache: readonly ManifestHookCacheEntry[], pipelineFingerprint: string | undefined): string | undefined;
32
34
  getManifestHookCache(): readonly ManifestHookCacheEntry[];
@@ -23,13 +23,6 @@ export type ContentAsset = {
23
23
  export type ContentManifestPluginAsset = PluginAsset & {
24
24
  path: string;
25
25
  };
26
- /**
27
- * The standard `article.*` positions the article consumer recognizes.
28
- *
29
- * These constants are a convenience for Sites that prefer named references;
30
- * writing the slot string inline (for example `name="article.metadata"`) stays
31
- * the recommended, most readable form in TSX layouts.
32
- */
33
26
  /** Client entries and their explicitly public configuration. */
34
27
  export type ContentManifestPluginClientEntry = PluginClientEntry;
35
28
  export type ContentManifestEntry = {
@@ -74,6 +74,9 @@ export type RiebeckitePlugin<TOptions = unknown> = {
74
74
  extendContentLocations?(context: PluginContentLocationAugmentContext): void | Promise<void>;
75
75
  onPostParsed?(context: PluginPostContext): void | Promise<void>;
76
76
  onPostProcessed?(context: PluginPostContext): void | Promise<void>;
77
+ manifestHookCache?: {
78
+ version: string;
79
+ };
77
80
  onManifestCreated?(context: PluginManifestContext): void | Promise<void>;
78
81
  extendMarkdownPipeline?(pipeline: MarkdownPipeline, context: MarkdownPipelineContext): void;
79
82
  extendHtmlPipeline?(pipeline: HtmlPipeline): void;
@@ -1,3 +1,4 @@
1
+ import type { ClientLifecycleScope } from "../client_lifecycle.js";
1
2
  import type { JsonValue } from "./json_value.js";
2
3
  export type PluginAssetKind = "style" | "script";
3
4
  export type PluginAsset = {
@@ -9,6 +10,8 @@ export type PluginClientEntry<TPublicConfig extends JsonValue = JsonValue> = {
9
10
  pluginName: string;
10
11
  moduleSpecifier: string;
11
12
  exportName?: string;
13
+ scope?: ClientLifecycleScope;
14
+ initializerArguments?: ClientInitializerArguments;
12
15
  /**
13
16
  * Configuration intentionally made available to this browser entry.
14
17
  *
@@ -18,8 +21,9 @@ export type PluginClientEntry<TPublicConfig extends JsonValue = JsonValue> = {
18
21
  */
19
22
  publicConfig?: TPublicConfig;
20
23
  };
24
+ export type ClientInitializerArguments = "config" | "root" | "context";
21
25
  export declare function createStyleAsset(pluginName: string, moduleSpecifier: string): PluginAsset;
22
- export declare function createClientEntry<TPublicConfig extends JsonValue>(pluginName: string, moduleSpecifier: string, exportName?: string, publicConfig?: TPublicConfig): PluginClientEntry<TPublicConfig>;
26
+ export declare function createClientEntry<TPublicConfig extends JsonValue>(pluginName: string, moduleSpecifier: string, exportName?: string, publicConfig?: TPublicConfig, scope?: ClientLifecycleScope, initializerArguments?: ClientInitializerArguments): PluginClientEntry<TPublicConfig>;
23
27
  /**
24
28
  * Serializes explicitly registered client configuration for code generation.
25
29
  * Rejecting non-JSON values prevents generated client modules from silently
@@ -59,6 +59,7 @@ export type PluginRenderTarget = {
59
59
  label: string;
60
60
  url: string;
61
61
  embed: boolean;
62
+ fragment?: string;
62
63
  };
63
64
  export type PluginRenderContext = PluginContext & PluginRenderTarget;
64
65
  export type PluginContentRenderer = {
@@ -72,6 +73,7 @@ export type PluginRenderInput = {
72
73
  label: string;
73
74
  url: string;
74
75
  embed: boolean;
76
+ fragment?: string;
75
77
  };
76
78
  export type PluginGeneratedHtml = {
77
79
  /** Output-relative path of the generated HTML file (e.g. "index.html"). */
@@ -36,6 +36,8 @@ export type PluginEndpoint = {
36
36
  path: string;
37
37
  /** Defaults to `GET` when omitted. */
38
38
  method?: PluginEndpointMethod;
39
+ /** Defaults to `true`; set to `false` for endpoints that only use configuration. */
40
+ requiresManifest?: boolean;
39
41
  handler(context: PluginEndpointContext): PluginEndpointResponse | Promise<PluginEndpointResponse>;
40
42
  };
41
43
  export type PluginEndpointOptions = {
@@ -26,6 +26,7 @@ export type PluginPageContext = PluginContext & {
26
26
  manifest: ContentManifest;
27
27
  /** Request pathname, normalized to an absolute path. */
28
28
  pathname: string;
29
+ renderMarkdown(markdown: string): Promise<string>;
29
30
  };
30
31
  export type PluginPageType = {
31
32
  /** Globally unique, stable page type identifier. */
@@ -1,6 +1,7 @@
1
1
  import type { ContentSource } from "../content/content_source.js";
2
2
  import type { RiebeckitePlugin } from "./plugin.js";
3
3
  import type { PublishStrategy } from "./publish_strategy.js";
4
+ import type { SpaConfig } from "./riebeckite_config.js";
4
5
  import type { SiteConfig } from "./site_config.js";
5
6
  import type { ThemeConfig, ThemeStyle } from "./theme_config.js";
6
7
  export type ResolvedRiebeckiteConfig = {
@@ -38,4 +39,5 @@ export type ResolvedRiebeckiteConfig = {
38
39
  enabled: boolean;
39
40
  directory: string;
40
41
  };
42
+ spa?: SpaConfig;
41
43
  };
@@ -3,6 +3,11 @@ import type { PluginInput } from "./plugin.js";
3
3
  import type { PublishStrategy } from "./publish_strategy.js";
4
4
  import type { SiteConfig } from "./site_config.js";
5
5
  import type { ThemeInput } from "./theme_config.js";
6
+ export type SpaConfig = {
7
+ prefetch?: false | "hover";
8
+ scrollRestoration?: boolean;
9
+ viewTransitions?: boolean;
10
+ };
6
11
  export interface RiebeckiteConfig {
7
12
  site: SiteConfig;
8
13
  content?: {
@@ -19,4 +24,5 @@ export interface RiebeckiteConfig {
19
24
  enabled?: boolean;
20
25
  directory?: string;
21
26
  };
27
+ spa?: boolean | SpaConfig;
22
28
  }
@@ -7,6 +7,9 @@ export type ThemeDesignTokens = {
7
7
  ink?: string;
8
8
  muted?: string;
9
9
  accent?: string;
10
+ link?: string;
11
+ focus?: string;
12
+ selected?: string;
10
13
  border?: string;
11
14
  borderStrong?: string;
12
15
  surface?: string;
@@ -15,6 +18,8 @@ export type ThemeDesignTokens = {
15
18
  danger?: string;
16
19
  success?: string;
17
20
  codeBackground?: string;
21
+ codeInk?: string;
22
+ codeInline?: string;
18
23
  };
19
24
  typography?: {
20
25
  bodyFont?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riebeckite/core",
3
- "version": "0.0.30",
3
+ "version": "0.0.32",
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.30"
52
+ "@riebeckite/test": "0.0.32"
53
53
  },
54
54
  "repository": {
55
55
  "type": "git",