@ampless/runtime 1.0.0-beta.66 → 1.0.0-beta.68
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/index.d.ts +90 -2
- package/dist/index.js +107 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Post, MediaMetadata, AmplessPlugin, ContentFieldRenderer, PluginPublicRenderContext, TiptapNodeHtmlAdapters,
|
|
1
|
+
import { Post, MediaMetadata, AmplessPlugin, ContentFieldRenderer, TiptapNodeMarkdownAdapters, PluginPublicRenderContext, TiptapNodeHtmlAdapters, Config, ThemeModule, ThemeManifest, PluginPackageManifest } from 'ampless';
|
|
2
2
|
export { Config, Post, ThemeManifest } from 'ampless';
|
|
3
3
|
import { Metadata } from 'next';
|
|
4
4
|
import { ReactNode } from 'react';
|
|
@@ -188,6 +188,21 @@ interface ContentFieldRegistry {
|
|
|
188
188
|
* render that happens to walk the conflicting node.
|
|
189
189
|
*/
|
|
190
190
|
declare function buildContentFieldRegistry(plugins: readonly AmplessPlugin[]): ContentFieldRegistry;
|
|
191
|
+
/**
|
|
192
|
+
* Merge every plugin's server-safe `tiptapNodeToMarkdown` map into a
|
|
193
|
+
* single `TiptapNodeMarkdownAdapters` registry. Eagerly errors on
|
|
194
|
+
* duplicate `nodeType` registration across plugins (two different
|
|
195
|
+
* functions) so the misuse surfaces at config time, not at the first
|
|
196
|
+
* markdown conversion that happens to walk the conflicting node. The
|
|
197
|
+
* exact same function registered twice (e.g. one shared adapter map
|
|
198
|
+
* reused by two plugin instances) is tolerated.
|
|
199
|
+
*
|
|
200
|
+
* Non-function entries are skipped with a warning rather than thrown:
|
|
201
|
+
* `definePlugin` already warns about them at definition time, and one
|
|
202
|
+
* broken plugin shouldn't take the whole site's markdown output down
|
|
203
|
+
* (a nodeType conflict, by contrast, is a config error — that throws).
|
|
204
|
+
*/
|
|
205
|
+
declare function buildMarkdownAdapterRegistry(plugins: readonly AmplessPlugin[]): TiptapNodeMarkdownAdapters;
|
|
191
206
|
interface RenderBodyOptions {
|
|
192
207
|
contentFields?: ContentFieldRegistry;
|
|
193
208
|
/**
|
|
@@ -280,6 +295,54 @@ declare function tiptapToMarkdown(doc: unknown, opts?: {
|
|
|
280
295
|
* relied on.
|
|
281
296
|
*/
|
|
282
297
|
declare function htmlToMarkdown(html: string): string;
|
|
298
|
+
interface PostToMarkdownOptions {
|
|
299
|
+
/**
|
|
300
|
+
* Per-nodeType tiptap→markdown serialisers (plugin embed nodes →
|
|
301
|
+
* bare URL lines). `createAmpless` injects
|
|
302
|
+
* `pluginHead.markdownAdapters`; a raw direct caller can omit this
|
|
303
|
+
* to fall back to the built-in switch (unknown atom nodes then emit
|
|
304
|
+
* unsupported-node placeholder comments).
|
|
305
|
+
*/
|
|
306
|
+
nodeAdapters?: TiptapNodeMarkdownAdapters;
|
|
307
|
+
/** Prepend YAML frontmatter (default: true). */
|
|
308
|
+
frontmatter?: boolean;
|
|
309
|
+
/**
|
|
310
|
+
* Absolute site origin used to build the frontmatter's `canonical`
|
|
311
|
+
* line (`<siteUrl>/<slug>`). Omitted → no canonical line.
|
|
312
|
+
*/
|
|
313
|
+
siteUrl?: string;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Convert a public post into its canonical Markdown representation:
|
|
317
|
+
* YAML frontmatter + format-dependent body. This is the single
|
|
318
|
+
* conversion point for every AI-readable surface (the `/<slug>.md`
|
|
319
|
+
* route, llms-full, public MCP `get_post`) — new surfaces should call
|
|
320
|
+
* this rather than reimplementing the format branches.
|
|
321
|
+
*
|
|
322
|
+
* Frontmatter carries **public fields only** (title / slug /
|
|
323
|
+
* publishedAt / updatedAt / tags / excerpt / canonical) — never
|
|
324
|
+
* postId, status, or metadata. Values are encoded with
|
|
325
|
+
* `JSON.stringify`: a JSON string is a valid YAML double-quoted
|
|
326
|
+
* scalar and a JSON array is a valid YAML flow sequence, so quoting /
|
|
327
|
+
* escaping is correct without a YAML library.
|
|
328
|
+
*
|
|
329
|
+
* Body quality varies by format:
|
|
330
|
+
* - `markdown`: the stored source, verbatim.
|
|
331
|
+
* - `tiptap`: `tiptapToMarkdown` with `opts.nodeAdapters` (plugin
|
|
332
|
+
* embeds become bare URL lines; unknown atom nodes emit an
|
|
333
|
+
* `ampless:unsupported-node` placeholder comment).
|
|
334
|
+
* - `html`: `htmlToMarkdown` — regex-based and approximate; complex
|
|
335
|
+
* markup may not fully survive (see htmlToMarkdown's limits).
|
|
336
|
+
* - `static`: no body conversion (the bundle bytes live in S3) — a
|
|
337
|
+
* markdown link to the served entrypoint, plus the excerpt when
|
|
338
|
+
* present.
|
|
339
|
+
*
|
|
340
|
+
* Pure, synchronous, and node-safe — same tier as `tiptapToMarkdown` /
|
|
341
|
+
* `htmlToMarkdown`. Themes / routes should usually call it through
|
|
342
|
+
* `ampless.postToMarkdown(post)`, which injects the plugin adapter
|
|
343
|
+
* registry and the effective site URL.
|
|
344
|
+
*/
|
|
345
|
+
declare function postToMarkdown(post: Post, opts?: PostToMarkdownOptions): string;
|
|
283
346
|
|
|
284
347
|
interface PluginHeadApi {
|
|
285
348
|
/**
|
|
@@ -328,6 +391,15 @@ interface PluginHeadApi {
|
|
|
328
391
|
* `contentFields`.
|
|
329
392
|
*/
|
|
330
393
|
readonly contentFieldsRegistry: ContentFieldRegistry;
|
|
394
|
+
/**
|
|
395
|
+
* Merged tiptap→markdown adapter registry across all valid plugins
|
|
396
|
+
* (server-safe `tiptapNodeToMarkdown` manifests). Built once at
|
|
397
|
+
* `createPluginHead` construction time so duplicate `nodeType`
|
|
398
|
+
* registrations across plugins throw eagerly (config error).
|
|
399
|
+
* Threaded into `rendering.ts:postToMarkdown()` via
|
|
400
|
+
* `Ampless.postToMarkdown`.
|
|
401
|
+
*/
|
|
402
|
+
readonly markdownAdapters: TiptapNodeMarkdownAdapters;
|
|
331
403
|
/**
|
|
332
404
|
* Resolve the per-plugin `PluginPublicRenderContext` snapshot used by
|
|
333
405
|
* `contentFields` renderers. Same `settings()` binding the public
|
|
@@ -652,6 +724,22 @@ interface Ampless {
|
|
|
652
724
|
* complete HTML documents that don't expand embed shortcuts.
|
|
653
725
|
*/
|
|
654
726
|
renderBodyHtmlString(post: Post): string;
|
|
727
|
+
/**
|
|
728
|
+
* Convert a post into its canonical Markdown representation (YAML
|
|
729
|
+
* frontmatter + body). This is the **single conversion point** for
|
|
730
|
+
* every AI-readable surface — the `/<slug>.md` route, llms-full, and
|
|
731
|
+
* the public MCP `get_post` all go through here rather than
|
|
732
|
+
* reimplementing the format branches. Plugin embed nodes are
|
|
733
|
+
* serialised via `pluginHead.markdownAdapters` (bare URL lines);
|
|
734
|
+
* `format: 'html'` bodies go through the regex-based `htmlToMarkdown`
|
|
735
|
+
* and are approximate. Async because the frontmatter's `canonical`
|
|
736
|
+
* line needs the effective `site.url` from the S3 site-settings
|
|
737
|
+
* cache; with `frontmatter: false` that read is skipped entirely and
|
|
738
|
+
* only the body is returned.
|
|
739
|
+
*/
|
|
740
|
+
postToMarkdown(post: Post, opts?: {
|
|
741
|
+
frontmatter?: boolean;
|
|
742
|
+
}): Promise<string>;
|
|
655
743
|
renderThemeCss(cssVars: Record<string, string>): string;
|
|
656
744
|
publicAssetUrl(key: string): string;
|
|
657
745
|
isStorageConfigured(): boolean;
|
|
@@ -677,4 +765,4 @@ interface Ampless {
|
|
|
677
765
|
*/
|
|
678
766
|
declare function createAmpless(opts: CreateAmplessOpts): Ampless;
|
|
679
767
|
|
|
680
|
-
export { type Ampless, type AmplessOutputs, COLOR_SCHEME_SETTING_KEY, type ColorScheme, type ContentFieldRegistry, type CreateAmplessOpts, DEFAULT_COLOR_SCHEME, type DataOutput, type EffectiveSiteSettings, type EffectiveThemeConfig, type ListPostsByTagOptions, type ListPostsOptions, type ListPostsResult, type MediaApi, type PluginHeadApi, type PluginSettingsApi, type PluginSettingsSnapshot, type PostsApi, type PublicHtmlForPostResult, type PublicMediaShape, type PublicPostConnectionShape, type PublicPostShape, type RenderBodyOptions, type ResolvedAssetMeta, type ResolvedMedia, type ResolvedTheme, SUPPORTED_API_VERSION, type SeoApi, type SiteSettingsApi, type StorageOutput, type StreamS3Options, type ThemeActiveApi, type ThemeConfigApi, type ThemesRegistry, _resetStreamS3Cache, buildContentFieldRegistry, createAmpless, createPluginHead, createPluginSettings, escapeJsonLdInlineBody, htmlToMarkdown, loadPackageManifest, markdownToHtml, renderBody, renderBodyHtmlString, renderThemeCss, streamS3Object, streamS3ObjectWithRunner, tiptapToHtml, tiptapToMarkdown, validateColorScheme };
|
|
768
|
+
export { type Ampless, type AmplessOutputs, COLOR_SCHEME_SETTING_KEY, type ColorScheme, type ContentFieldRegistry, type CreateAmplessOpts, DEFAULT_COLOR_SCHEME, type DataOutput, type EffectiveSiteSettings, type EffectiveThemeConfig, type ListPostsByTagOptions, type ListPostsOptions, type ListPostsResult, type MediaApi, type PluginHeadApi, type PluginSettingsApi, type PluginSettingsSnapshot, type PostToMarkdownOptions, type PostsApi, type PublicHtmlForPostResult, type PublicMediaShape, type PublicPostConnectionShape, type PublicPostShape, type RenderBodyOptions, type ResolvedAssetMeta, type ResolvedMedia, type ResolvedTheme, SUPPORTED_API_VERSION, type SeoApi, type SiteSettingsApi, type StorageOutput, type StreamS3Options, type ThemeActiveApi, type ThemeConfigApi, type ThemesRegistry, _resetStreamS3Cache, buildContentFieldRegistry, buildMarkdownAdapterRegistry, createAmpless, createPluginHead, createPluginSettings, escapeJsonLdInlineBody, htmlToMarkdown, loadPackageManifest, markdownToHtml, postToMarkdown, renderBody, renderBodyHtmlString, renderThemeCss, streamS3Object, streamS3ObjectWithRunner, tiptapToHtml, tiptapToMarkdown, validateColorScheme };
|
package/dist/index.js
CHANGED
|
@@ -399,6 +399,29 @@ function buildContentFieldRegistry(plugins) {
|
|
|
399
399
|
}
|
|
400
400
|
return { tiptap, markdownUrl, htmlPlaceholder };
|
|
401
401
|
}
|
|
402
|
+
function buildMarkdownAdapterRegistry(plugins) {
|
|
403
|
+
const merged = {};
|
|
404
|
+
for (const plugin of plugins) {
|
|
405
|
+
const adapters = plugin.tiptapNodeToMarkdown;
|
|
406
|
+
if (!adapters) continue;
|
|
407
|
+
for (const [nodeType, adapter] of Object.entries(adapters)) {
|
|
408
|
+
if (typeof adapter !== "function") {
|
|
409
|
+
console.warn(
|
|
410
|
+
`[ampless markdownAdapters] plugin "${plugin.instanceId ?? plugin.name}" declares a non-function tiptapNodeToMarkdown entry for nodeType "${nodeType}". Skipping the entry.`
|
|
411
|
+
);
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
const existing = merged[nodeType];
|
|
415
|
+
if (existing && existing !== adapter) {
|
|
416
|
+
throw new Error(
|
|
417
|
+
`[ampless markdownAdapters] duplicate tiptap nodeType "${nodeType}" \u2014 already registered by another plugin. Each nodeType may be claimed by at most one plugin.`
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
merged[nodeType] = adapter;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return merged;
|
|
424
|
+
}
|
|
402
425
|
function htmlPassthrough(html) {
|
|
403
426
|
return createElement("span", { dangerouslySetInnerHTML: { __html: html } });
|
|
404
427
|
}
|
|
@@ -776,9 +799,19 @@ function tiptapNodeToMarkdown(node, opts) {
|
|
|
776
799
|
`;
|
|
777
800
|
}
|
|
778
801
|
default:
|
|
779
|
-
return children;
|
|
802
|
+
if (children) return children;
|
|
803
|
+
console.warn(
|
|
804
|
+
`[ampless] tiptapToMarkdown: no adapter for node type "${String(node.type)}" \u2014 emitting placeholder`
|
|
805
|
+
);
|
|
806
|
+
return `
|
|
807
|
+
<!-- ampless:unsupported-node type="${sanitizeNodeTypeForComment(node.type)}" -->
|
|
808
|
+
|
|
809
|
+
`;
|
|
780
810
|
}
|
|
781
811
|
}
|
|
812
|
+
function sanitizeNodeTypeForComment(type) {
|
|
813
|
+
return String(type).replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 64);
|
|
814
|
+
}
|
|
782
815
|
function tiptapTableToMarkdown(node, opts) {
|
|
783
816
|
const rows = node.content ?? [];
|
|
784
817
|
if (rows.length === 0) return "";
|
|
@@ -902,6 +935,62 @@ function convertHtmlTaskList(items) {
|
|
|
902
935
|
}
|
|
903
936
|
return out.join("\n");
|
|
904
937
|
}
|
|
938
|
+
function postToMarkdown(post, opts = {}) {
|
|
939
|
+
const frontmatter = opts.frontmatter !== false ? postFrontmatter(post, opts.siteUrl) : "";
|
|
940
|
+
return frontmatter + postBodyToMarkdown(post, opts);
|
|
941
|
+
}
|
|
942
|
+
function postFrontmatter(post, siteUrl) {
|
|
943
|
+
const lines = [];
|
|
944
|
+
lines.push(`title: ${JSON.stringify(post.title)}`);
|
|
945
|
+
lines.push(`slug: ${JSON.stringify(post.slug)}`);
|
|
946
|
+
if (post.publishedAt) lines.push(`publishedAt: ${JSON.stringify(post.publishedAt)}`);
|
|
947
|
+
if (post.updatedAt) lines.push(`updatedAt: ${JSON.stringify(post.updatedAt)}`);
|
|
948
|
+
if (post.tags && post.tags.length > 0) lines.push(`tags: ${JSON.stringify(post.tags)}`);
|
|
949
|
+
if (post.excerpt) lines.push(`excerpt: ${JSON.stringify(post.excerpt)}`);
|
|
950
|
+
if (siteUrl) {
|
|
951
|
+
const origin = siteUrl.replace(/\/+$/, "");
|
|
952
|
+
lines.push(`canonical: ${JSON.stringify(`${origin}/${post.slug}`)}`);
|
|
953
|
+
}
|
|
954
|
+
return `---
|
|
955
|
+
${lines.join("\n")}
|
|
956
|
+
---
|
|
957
|
+
|
|
958
|
+
`;
|
|
959
|
+
}
|
|
960
|
+
function postBodyToMarkdown(post, opts) {
|
|
961
|
+
switch (post.format) {
|
|
962
|
+
case "markdown":
|
|
963
|
+
return typeof post.body === "string" ? post.body : String(post.body ?? "");
|
|
964
|
+
case "tiptap":
|
|
965
|
+
return tiptapToMarkdown(post.body, { nodeAdapters: opts.nodeAdapters });
|
|
966
|
+
case "html":
|
|
967
|
+
return htmlToMarkdown(String(post.body));
|
|
968
|
+
case "static": {
|
|
969
|
+
const link = `[${staticEntrypoint(post.body)}](/${post.slug}/)`;
|
|
970
|
+
return post.excerpt ? `${link}
|
|
971
|
+
|
|
972
|
+
${post.excerpt}
|
|
973
|
+
` : `${link}
|
|
974
|
+
`;
|
|
975
|
+
}
|
|
976
|
+
default:
|
|
977
|
+
return "";
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
function staticEntrypoint(body) {
|
|
981
|
+
let manifest = body;
|
|
982
|
+
if (typeof manifest === "string") {
|
|
983
|
+
try {
|
|
984
|
+
manifest = JSON.parse(manifest);
|
|
985
|
+
} catch {
|
|
986
|
+
return "index.html";
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
if (typeof manifest === "object" && manifest !== null && typeof manifest.entrypoint === "string" && manifest.entrypoint.length > 0) {
|
|
990
|
+
return manifest.entrypoint;
|
|
991
|
+
}
|
|
992
|
+
return "index.html";
|
|
993
|
+
}
|
|
905
994
|
|
|
906
995
|
// src/plugin-package-manifest.ts
|
|
907
996
|
var TRUST_LEVELS = ["untrusted", "trusted", "privileged"];
|
|
@@ -1520,6 +1609,7 @@ function createPluginHead(cmsConfig, pluginSettings) {
|
|
|
1520
1609
|
}
|
|
1521
1610
|
}
|
|
1522
1611
|
const contentFieldsRegistry = buildContentFieldRegistry(validPlugins);
|
|
1612
|
+
const markdownAdapters = buildMarkdownAdapterRegistry(validPlugins);
|
|
1523
1613
|
for (const plugin of validPlugins) {
|
|
1524
1614
|
const caps = plugin.capabilities;
|
|
1525
1615
|
if (!caps) continue;
|
|
@@ -1581,6 +1671,7 @@ function createPluginHead(cmsConfig, pluginSettings) {
|
|
|
1581
1671
|
return collectPostScriptsForPage(validPlugins, cmsConfig.site, snapshot, posts);
|
|
1582
1672
|
},
|
|
1583
1673
|
contentFieldsRegistry,
|
|
1674
|
+
markdownAdapters,
|
|
1584
1675
|
async contextForPlugins() {
|
|
1585
1676
|
const snapshot = await pluginSettings.loadAll();
|
|
1586
1677
|
return (plugin) => makeCtx(plugin, cmsConfig.site, snapshot);
|
|
@@ -1847,6 +1938,19 @@ function createAmpless(opts) {
|
|
|
1847
1938
|
});
|
|
1848
1939
|
},
|
|
1849
1940
|
renderBodyHtmlString: (post) => renderBodyHtmlString(post),
|
|
1941
|
+
postToMarkdown: async (post, o) => {
|
|
1942
|
+
if (o?.frontmatter === false) {
|
|
1943
|
+
return postToMarkdown(post, {
|
|
1944
|
+
nodeAdapters: pluginHead.markdownAdapters,
|
|
1945
|
+
frontmatter: false
|
|
1946
|
+
});
|
|
1947
|
+
}
|
|
1948
|
+
const { site } = await settings.loadSiteSettings();
|
|
1949
|
+
return postToMarkdown(post, {
|
|
1950
|
+
nodeAdapters: pluginHead.markdownAdapters,
|
|
1951
|
+
siteUrl: site.url || void 0
|
|
1952
|
+
});
|
|
1953
|
+
},
|
|
1850
1954
|
renderThemeCss: (cssVars) => renderThemeCss(cssVars),
|
|
1851
1955
|
publicAssetUrl: (key) => storage.publicAssetUrl(key),
|
|
1852
1956
|
isStorageConfigured: () => storage.isStorageConfigured(),
|
|
@@ -1870,6 +1974,7 @@ export {
|
|
|
1870
1974
|
SUPPORTED_API_VERSION,
|
|
1871
1975
|
_resetStreamS3Cache,
|
|
1872
1976
|
buildContentFieldRegistry,
|
|
1977
|
+
buildMarkdownAdapterRegistry,
|
|
1873
1978
|
createAmpless,
|
|
1874
1979
|
createPluginHead,
|
|
1875
1980
|
createPluginSettings,
|
|
@@ -1877,6 +1982,7 @@ export {
|
|
|
1877
1982
|
htmlToMarkdown,
|
|
1878
1983
|
loadPackageManifest,
|
|
1879
1984
|
markdownToHtml,
|
|
1985
|
+
postToMarkdown,
|
|
1880
1986
|
renderBody,
|
|
1881
1987
|
renderBodyHtmlString,
|
|
1882
1988
|
renderThemeCss,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ampless/runtime",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.68",
|
|
4
4
|
"description": "Public-side runtime for ampless: post fetching, theme dispatch, middleware, public route handlers",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"marked": "^18.0.4",
|
|
52
52
|
"sanitize-html": "^2.17.4",
|
|
53
53
|
"tailwind-merge": "^3.6.0",
|
|
54
|
-
"@ampless/plugin-og-image": "0.2.0-beta.
|
|
55
|
-
"ampless": "1.0.0-beta.
|
|
54
|
+
"@ampless/plugin-og-image": "0.2.0-beta.55",
|
|
55
|
+
"ampless": "1.0.0-beta.55"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"@aws-amplify/adapter-nextjs": "^1",
|